liquid-3.0.6/0000755000004100000410000000000012563012415013036 5ustar www-datawww-dataliquid-3.0.6/MIT-LICENSE0000644000004100000410000000204712563012415014475 0ustar www-datawww-dataCopyright (c) 2005, 2006 Tobias Luetke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. liquid-3.0.6/lib/0000755000004100000410000000000012563012415013604 5ustar www-datawww-dataliquid-3.0.6/lib/liquid/0000755000004100000410000000000012563012415015073 5ustar www-datawww-dataliquid-3.0.6/lib/liquid/condition.rb0000644000004100000410000000606712563012415017417 0ustar www-datawww-datamodule Liquid # Container for liquid nodes which conveniently wraps decision making logic # # Example: # # c = Condition.new('1', '==', '1') # c.evaluate #=> true # class Condition #:nodoc: @@operators = { '=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) }, '!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) }, '<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) }, '<'.freeze => :<, '>'.freeze => :>, '>='.freeze => :>=, '<='.freeze => :<=, 'contains'.freeze => lambda { |cond, left, right| left && right && left.respond_to?(:include?) ? left.include?(right) : false } } def self.operators @@operators end attr_reader :attachment attr_accessor :left, :operator, :right def initialize(left = nil, operator = nil, right = nil) @left = left @operator = operator @right = right @child_relation = nil @child_condition = nil end def evaluate(context = Context.new) result = interpret_condition(left, right, operator, context) case @child_relation when :or result || @child_condition.evaluate(context) when :and result && @child_condition.evaluate(context) else result end end def or(condition) @child_relation = :or @child_condition = condition end def and(condition) @child_relation = :and @child_condition = condition end def attach(attachment) @attachment = attachment end def else? false end def inspect "#" end private def equal_variables(left, right) if left.is_a?(Symbol) if right.respond_to?(left) return right.send(left.to_s) else return nil end end if right.is_a?(Symbol) if left.respond_to?(right) return left.send(right.to_s) else return nil end end left == right end def interpret_condition(left, right, op, context) # If the operator is empty this means that the decision statement is just # a single variable. We can just poll this variable from the context and # return this as the result. return context[left] if op == nil left = context[left] right = context[right] operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}")) if operation.respond_to?(:call) operation.call(self, left, right) elsif left.respond_to?(operation) and right.respond_to?(operation) begin left.send(operation, right) rescue ::ArgumentError => e raise Liquid::ArgumentError.new(e.message) end else nil end end end class ElseCondition < Condition def else? true end def evaluate(context) true end end end liquid-3.0.6/lib/liquid/tags/0000755000004100000410000000000012563012415016031 5ustar www-datawww-dataliquid-3.0.6/lib/liquid/tags/case.rb0000644000004100000410000000355012563012415017274 0ustar www-datawww-datamodule Liquid class Case < Block Syntax = /(#{QuotedFragment})/o WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om def initialize(tag_name, markup, options) super @blocks = [] if markup =~ Syntax @left = $1 else raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze)) end end def nodelist @blocks.flat_map(&:attachment) end def unknown_tag(tag, markup, tokens) @nodelist = [] case tag when 'when'.freeze record_when_condition(markup) when 'else'.freeze record_else_condition(markup) else super end end def render(context) context.stack do execute_else_block = true output = '' @blocks.each do |block| if block.else? return render_all(block.attachment, context) if execute_else_block elsif block.evaluate(context) execute_else_block = false output << render_all(block.attachment, context) end end output end end private def record_when_condition(markup) while markup # Create a new nodelist and assign it to the new block if not markup =~ WhenSyntax raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze)) end markup = $2 block = Condition.new(@left, '=='.freeze, $1) block.attach(@nodelist) @blocks.push(block) end end def record_else_condition(markup) if not markup.strip.empty? raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze)) end block = ElseCondition.new block.attach(@nodelist) @blocks << block end end Template.register_tag('case'.freeze, Case) end liquid-3.0.6/lib/liquid/tags/comment.rb0000644000004100000410000000035112563012415020017 0ustar www-datawww-datamodule Liquid class Comment < Block def render(context) ''.freeze end def unknown_tag(tag, markup, tokens) end def blank? true end end Template.register_tag('comment'.freeze, Comment) end liquid-3.0.6/lib/liquid/tags/capture.rb0000644000004100000410000000155412563012415020026 0ustar www-datawww-datamodule Liquid # Capture stores the result of a block into a variable without rendering it inplace. # # {% capture heading %} # Monkeys! # {% endcapture %} # ... #

{{ heading }}

# # Capture is useful for saving content for use later in your template, such as # in a sidebar or footer. # class Capture < Block Syntax = /(#{VariableSignature}+)/o def initialize(tag_name, markup, options) super if markup =~ Syntax @to = $1 else raise SyntaxError.new(options[:locale].t("errors.syntax.capture")) end end def render(context) output = super context.scopes.last[@to] = output context.increment_used_resources(:assign_score_current, output) ''.freeze end def blank? true end end Template.register_tag('capture'.freeze, Capture) end liquid-3.0.6/lib/liquid/tags/continue.rb0000644000004100000410000000056712563012415020212 0ustar www-datawww-datamodule Liquid # Continue tag to be used to break out of a for loop. # # == Basic Usage: # {% for item in collection %} # {% if item.condition %} # {% continue %} # {% endif %} # {% endfor %} # class Continue < Tag def interrupt ContinueInterrupt.new end end Template.register_tag('continue'.freeze, Continue) end liquid-3.0.6/lib/liquid/tags/table_row.rb0000644000004100000410000000377712563012415020352 0ustar www-datawww-datamodule Liquid class TableRow < Block Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o def initialize(tag_name, markup, options) super if markup =~ Syntax @variable_name = $1 @collection_name = $2 @attributes = {} markup.scan(TagAttributes) do |key, value| @attributes[key] = value end else raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze)) end end def render(context) collection = context[@collection_name] or return ''.freeze from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0 to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil collection = Utils.slice_collection(collection, from, to) length = collection.length cols = context[@attributes['cols'.freeze]].to_i row = 1 col = 0 result = "\n" context.stack do collection.each_with_index do |item, index| context[@variable_name] = item context['tablerowloop'.freeze] = { 'length'.freeze => length, 'index'.freeze => index + 1, 'index0'.freeze => index, 'col'.freeze => col + 1, 'col0'.freeze => col, 'rindex'.freeze => length - index, 'rindex0'.freeze => length - index - 1, 'first'.freeze => (index == 0), 'last'.freeze => (index == length - 1), 'col_first'.freeze => (col == 0), 'col_last'.freeze => (col == cols - 1) } col += 1 result << "" << super << '' if col == cols and (index != length - 1) col = 0 row += 1 result << "\n" end end end result << "\n" result end end Template.register_tag('tablerow'.freeze, TableRow) end liquid-3.0.6/lib/liquid/tags/ifchanged.rb0000644000004100000410000000054712563012415020274 0ustar www-datawww-datamodule Liquid class Ifchanged < Block def render(context) context.stack do output = super if output != context.registers[:ifchanged] context.registers[:ifchanged] = output output else ''.freeze end end end end Template.register_tag('ifchanged'.freeze, Ifchanged) end liquid-3.0.6/lib/liquid/tags/include.rb0000644000004100000410000000564412563012415020012 0ustar www-datawww-datamodule Liquid # Include allows templates to relate with other templates # # Simply include another template: # # {% include 'product' %} # # Include a template with a local variable: # # {% include 'product' with products[0] %} # # Include a template for a collection: # # {% include 'product' for products %} # class Include < Tag Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o def initialize(tag_name, markup, options) super if markup =~ Syntax @template_name = $1 @variable_name = $3 @attributes = {} markup.scan(TagAttributes) do |key, value| @attributes[key] = value end else raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze)) end end def parse(tokens) end def render(context) partial = load_cached_partial(context) variable = context[@variable_name || @template_name[1..-2]] context.stack do @attributes.each do |key, value| context[key] = context[value] end context_variable_name = @template_name[1..-2].split('/'.freeze).last if variable.is_a?(Array) variable.collect do |var| context[context_variable_name] = var partial.render(context) end else context[context_variable_name] = variable partial.render(context) end end end private def load_cached_partial(context) cached_partials = context.registers[:cached_partials] || {} template_name = context[@template_name] if cached = cached_partials[template_name] return cached end source = read_template_from_file_system(context) partial = Liquid::Template.parse(source, pass_options) 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 # make read_template_file call backwards-compatible. case file_system.method(:read_template_file).arity when 1 file_system.read_template_file(context[@template_name]) when 2 file_system.read_template_file(context[@template_name], context) else raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)" end end def pass_options dont_pass = @options[:include_options_blacklist] return {locale: @options[:locale]} if dont_pass == true opts = @options.merge(included: true, include_options_blacklist: false) if dont_pass.is_a?(Array) dont_pass.each {|o| opts.delete(o)} end opts end end Template.register_tag('include'.freeze, Include) end liquid-3.0.6/lib/liquid/tags/cycle.rb0000644000004100000410000000306612563012415017462 0ustar www-datawww-datamodule Liquid # Cycle is usually used within a loop to alternate between values, like colors or DOM classes. # # {% for item in items %} #
{{ item }}
# {% end %} # #
Item one
#
Item two
#
Item three
#
Item four
#
Item five
# class Cycle < Tag SimpleSyntax = /\A#{QuotedFragment}+/o NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om def initialize(tag_name, markup, options) super case markup when NamedSyntax @variables = variables_from_string($2) @name = $1 when SimpleSyntax @variables = variables_from_string(markup) @name = "'#{@variables.to_s}'" else raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze)) end end def render(context) context.registers[:cycle] ||= Hash.new(0) context.stack do key = context[@name] iteration = context.registers[:cycle][key] result = context[@variables[iteration]] iteration += 1 iteration = 0 if iteration >= @variables.size context.registers[:cycle][key] = iteration result end end private def variables_from_string(markup) markup.split(',').collect do |var| var =~ /\s*(#{QuotedFragment})\s*/o $1 ? $1 : nil end.compact end end Template.register_tag('cycle', Cycle) end liquid-3.0.6/lib/liquid/tags/assign.rb0000644000004100000410000000147412563012415017650 0ustar www-datawww-datamodule Liquid # Assign sets a variable in your template. # # {% assign foo = 'monkey' %} # # You can then use the variable later in the page. # # {{ foo }} # class Assign < Tag Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om def initialize(tag_name, markup, options) super if markup =~ Syntax @to = $1 @from = Variable.new($2,options) @from.line_number = line_number else raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze) end end def render(context) val = @from.render(context) context.scopes.last[@to] = val context.increment_used_resources(:assign_score_current, val) ''.freeze end def blank? true end end Template.register_tag('assign'.freeze, Assign) end liquid-3.0.6/lib/liquid/tags/decrement.rb0000644000004100000410000000166212563012415020331 0ustar www-datawww-datamodule Liquid # decrement is used in a place where one needs to insert a counter # into a template, and needs the counter to survive across # multiple instantiations of the template. # NOTE: decrement is a pre-decrement, --i, # while increment is post: i++. # # (To achieve the survival, the application must keep the context) # # if the variable does not exist, it is created with value 0. # Hello: {% decrement variable %} # # gives you: # # Hello: -1 # Hello: -2 # Hello: -3 # class Decrement < Tag def initialize(tag_name, markup, options) super @variable = markup.strip end def render(context) value = context.environments.first[@variable] ||= 0 value = value - 1 context.environments.first[@variable] = value value.to_s end private end Template.register_tag('decrement'.freeze, Decrement) end liquid-3.0.6/lib/liquid/tags/for.rb0000644000004100000410000001242212563012415017145 0ustar www-datawww-datamodule Liquid # "For" iterates over an array or collection. # Several useful variables are available to you within the loop. # # == Basic usage: # {% for item in collection %} # {{ forloop.index }}: {{ item.name }} # {% endfor %} # # == Advanced usage: # {% for item in collection %} #
# Item {{ forloop.index }}: {{ item.name }} #
# {% else %} # There is nothing in the collection. # {% endfor %} # # You can also define a limit and offset much like SQL. Remember # that offset starts at 0 for the first item. # # {% for item in collection limit:5 offset:10 %} # {{ item.name }} # {% end %} # # To reverse the for loop simply use {% for item in collection reversed %} # # == Available variables: # # forloop.name:: 'item-collection' # forloop.length:: Length of the loop # forloop.index:: The current item's position in the collection; # forloop.index starts at 1. # This is helpful for non-programmers who start believe # the first item in an array is 1, not 0. # forloop.index0:: The current item's position in the collection # where the first item is 0 # forloop.rindex:: Number of items remaining in the loop # (length - index) where 1 is the last item. # forloop.rindex0:: Number of items remaining in the loop # where 0 is the last item. # forloop.first:: Returns true if the item is the first item. # forloop.last:: Returns true if the item is the last item. # class For < Block Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o def initialize(tag_name, markup, options) super parse_with_selected_parser(markup) @nodelist = @for_block = [] end def nodelist if @else_block @for_block + @else_block else @for_block end end def unknown_tag(tag, markup, tokens) return super unless tag == 'else'.freeze @nodelist = @else_block = [] end def render(context) context.registers[:for] ||= Hash.new(0) collection = context[@collection_name] collection = collection.to_a if collection.is_a?(Range) # Maintains Ruby 1.8.7 String#each behaviour on 1.9 return render_else(context) unless iterable?(collection) from = if @attributes['offset'.freeze] == 'continue'.freeze context.registers[:for][@name].to_i else context[@attributes['offset'.freeze]].to_i end limit = context[@attributes['limit'.freeze]] to = limit ? limit.to_i + from : nil segment = Utils.slice_collection(collection, from, to) return render_else(context) if segment.empty? segment.reverse! if @reversed result = '' length = segment.length # Store our progress through the collection for the continue flag context.registers[:for][@name] = from + segment.length context.stack do segment.each_with_index do |item, index| context[@variable_name] = item context['forloop'.freeze] = { 'name'.freeze => @name, 'length'.freeze => length, 'index'.freeze => index + 1, 'index0'.freeze => index, 'rindex'.freeze => length - index, 'rindex0'.freeze => length - index - 1, 'first'.freeze => (index == 0), 'last'.freeze => (index == length - 1) } result << render_all(@for_block, context) # Handle any interrupts if they exist. if context.has_interrupt? interrupt = context.pop_interrupt break if interrupt.is_a? BreakInterrupt next if interrupt.is_a? ContinueInterrupt end end end result end protected def lax_parse(markup) if markup =~ Syntax @variable_name = $1 @collection_name = $2 @name = "#{$1}-#{$2}" @reversed = $3 @attributes = {} markup.scan(TagAttributes) do |key, value| @attributes[key] = value end else raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze)) end end def strict_parse(markup) p = Parser.new(markup) @variable_name = p.consume(:id) raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze) @collection_name = p.expression @name = "#{@variable_name}-#{@collection_name}" @reversed = p.id?('reversed'.freeze) @attributes = {} while p.look(:id) && p.look(:colon, 1) unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze) raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze)) end p.consume val = p.expression @attributes[attribute] = val end p.consume(:end_of_string) end private def render_else(context) return @else_block ? [render_all(@else_block, context)] : ''.freeze end def iterable?(collection) collection.respond_to?(:each) || Utils.non_blank_string?(collection) end end Template.register_tag('for'.freeze, For) end liquid-3.0.6/lib/liquid/tags/raw.rb0000644000004100000410000000073612563012415017155 0ustar www-datawww-datamodule Liquid class Raw < Block FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om def parse(tokens) @nodelist ||= [] @nodelist.clear while token = tokens.shift if token =~ FullTokenPossiblyInvalid @nodelist << $1 if $1 != "".freeze return if block_delimiter == $2 end @nodelist << token if not token.empty? end end end Template.register_tag('raw'.freeze, Raw) end liquid-3.0.6/lib/liquid/tags/unless.rb0000644000004100000410000000144412563012415017672 0ustar www-datawww-datarequire File.dirname(__FILE__) + '/if' module Liquid # Unless is a conditional just like 'if' but works on the inverse logic. # # {% unless x < 0 %} x is greater than zero {% endunless %} # class Unless < If def render(context) context.stack do # First condition is interpreted backwards ( if not ) first_block = @blocks.first unless first_block.evaluate(context) return render_all(first_block.attachment, context) end # After the first condition unless works just like if @blocks[1..-1].each do |block| if block.evaluate(context) return render_all(block.attachment, context) end end ''.freeze end end end Template.register_tag('unless'.freeze, Unless) end liquid-3.0.6/lib/liquid/tags/increment.rb0000644000004100000410000000145212563012415020344 0ustar www-datawww-datamodule Liquid # increment is used in a place where one needs to insert a counter # into a template, and needs the counter to survive across # multiple instantiations of the template. # (To achieve the survival, the application must keep the context) # # if the variable does not exist, it is created with value 0. # # Hello: {% increment variable %} # # gives you: # # Hello: 0 # Hello: 1 # Hello: 2 # class Increment < Tag def initialize(tag_name, markup, options) super @variable = markup.strip end def render(context) value = context.environments.first[@variable] ||= 0 context.environments.first[@variable] = value + 1 value.to_s end end Template.register_tag('increment'.freeze, Increment) end liquid-3.0.6/lib/liquid/tags/if.rb0000644000004100000410000000537212563012415016763 0ustar www-datawww-datamodule Liquid # If is the conditional block # # {% if user.admin %} # Admin user! # {% else %} # Not admin user # {% endif %} # # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need. # class If < Block Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o BOOLEAN_OPERATORS = %w(and or) def initialize(tag_name, markup, options) super @blocks = [] push_block('if'.freeze, markup) end def nodelist @blocks.flat_map(&:attachment) end def unknown_tag(tag, markup, tokens) if ['elsif'.freeze, 'else'.freeze].include?(tag) push_block(tag, markup) else super end end def render(context) context.stack do @blocks.each do |block| if block.evaluate(context) return render_all(block.attachment, context) end end ''.freeze end end private def push_block(tag, markup) block = if tag == 'else'.freeze ElseCondition.new else parse_with_selected_parser(markup) end @blocks.push(block) @nodelist = block.attach(Array.new) end def lax_parse(markup) expressions = markup.scan(ExpressionsAndOperators) raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax condition = Condition.new($1, $2, $3) while not expressions.empty? operator = expressions.pop.to_s.strip raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax new_condition = Condition.new($1, $2, $3) raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator) new_condition.send(operator, condition) condition = new_condition end condition end def strict_parse(markup) p = Parser.new(markup) condition = parse_binary_comparison(p) p.consume(:end_of_string) condition end def parse_binary_comparison(p) condition = parse_comparison(p) if op = (p.id?('and'.freeze) || p.id?('or'.freeze)) condition.send(op, parse_binary_comparison(p)) end condition end def parse_comparison(p) a = p.expression if op = p.consume?(:comparison) b = p.expression Condition.new(a, op, b) else Condition.new(a) end end end Template.register_tag('if'.freeze, If) end liquid-3.0.6/lib/liquid/tags/break.rb0000644000004100000410000000055012563012415017442 0ustar www-datawww-datamodule Liquid # Break tag to be used to break out of a for loop. # # == Basic Usage: # {% for item in collection %} # {% if item.condition %} # {% break %} # {% endif %} # {% endfor %} # class Break < Tag def interrupt BreakInterrupt.new end end Template.register_tag('break'.freeze, Break) end liquid-3.0.6/lib/liquid/utils.rb0000644000004100000410000000147612563012415016570 0ustar www-datawww-datamodule Liquid module Utils def self.slice_collection(collection, from, to) if (from != 0 || to != nil) && collection.respond_to?(:load_slice) collection.load_slice(from, to) else slice_collection_using_each(collection, from, to) end end def self.non_blank_string?(collection) collection.is_a?(String) && collection != ''.freeze end def self.slice_collection_using_each(collection, from, to) segments = [] index = 0 # Maintains Ruby 1.8.7 String#each behaviour on 1.9 return [collection] if non_blank_string?(collection) collection.each do |item| if to && to <= index break end if from <= index segments << item end index += 1 end segments end end end liquid-3.0.6/lib/liquid/block_body.rb0000644000004100000410000000752012563012415017533 0ustar www-datawww-datamodule Liquid class BlockBody FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om TAGSTART = "{%".freeze VARSTART = "{{".freeze attr_reader :nodelist def initialize @nodelist = [] @blank = true end def parse(tokens, options) while token = tokens.shift begin unless token.empty? case when token.start_with?(TAGSTART) if token =~ FullToken tag_name = $1 markup = $2 # fetch the tag from registered blocks if tag = Template.tags[tag_name] markup = token.child(markup) if token.is_a?(Token) new_tag = tag.parse(tag_name, markup, tokens, options) new_tag.line_number = token.line_number if token.is_a?(Token) @blank &&= new_tag.blank? @nodelist << new_tag else # end parsing if we reach an unknown tag and let the caller decide # determine how to proceed return yield tag_name, markup end else raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect)) end when token.start_with?(VARSTART) new_var = create_variable(token, options) new_var.line_number = token.line_number if token.is_a?(Token) @nodelist << new_var @blank = false else @nodelist << token @blank &&= !!(token =~ /\A\s*\z/) end end rescue SyntaxError => e e.set_line_number_from_token(token) raise end end yield nil, nil end def blank? @blank end def warnings all_warnings = [] nodelist.each do |node| all_warnings.concat(node.warnings) if node.respond_to?(:warnings) && node.warnings end all_warnings end def render(context) output = [] context.resource_limits[:render_length_current] = 0 context.resource_limits[:render_score_current] += @nodelist.length @nodelist.each do |token| # Break out if we have any unhanded interrupts. break if context.has_interrupt? begin # If we get an Interrupt that means the block must stop processing. An # Interrupt is any command that stops block execution such as {% break %} # or {% continue %} if token.is_a?(Continue) or token.is_a?(Break) context.push_interrupt(token.interrupt) break end token_output = render_token(token, context) unless token.is_a?(Block) && token.blank? output << token_output end rescue MemoryError => e raise e rescue ::StandardError => e output << context.handle_error(e, token) end end output.join end private def render_token(token, context) token_output = (token.respond_to?(:render) ? token.render(context) : token) context.increment_used_resources(:render_length_current, token_output) if context.resource_limits_reached? context.resource_limits[:reached] = true raise MemoryError.new("Memory limits exceeded".freeze) end token_output end def create_variable(token, options) token.scan(ContentOfVariable) do |content| markup = token.is_a?(Token) ? token.child(content.first) : content.first return Variable.new(markup, options) end raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect)) end end end liquid-3.0.6/lib/liquid/lexer.rb0000644000004100000410000000250612563012415016542 0ustar www-datawww-datarequire "strscan" module Liquid class Lexer SPECIALS = { '|'.freeze => :pipe, '.'.freeze => :dot, ':'.freeze => :colon, ','.freeze => :comma, '['.freeze => :open_square, ']'.freeze => :close_square, '('.freeze => :open_round, ')'.freeze => :close_round } IDENTIFIER = /[\w\-?!]+/ SINGLE_STRING_LITERAL = /'[^\']*'/ DOUBLE_STRING_LITERAL = /"[^\"]*"/ NUMBER_LITERAL = /-?\d+(\.\d+)?/ DOTDOT = /\.\./ COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/ def initialize(input) @ss = StringScanner.new(input.rstrip) end def tokenize @output = [] while !@ss.eos? @ss.skip(/\s*/) tok = case when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t] when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t] when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t] when t = @ss.scan(NUMBER_LITERAL) then [:number, t] when t = @ss.scan(IDENTIFIER) then [:id, t] when t = @ss.scan(DOTDOT) then [:dotdot, t] else c = @ss.getch if s = SPECIALS[c] [s,c] else raise SyntaxError, "Unexpected character #{c}" end end @output << tok end @output << [:end_of_string] end end end liquid-3.0.6/lib/liquid/errors.rb0000644000004100000410000000226412563012415016740 0ustar www-datawww-datamodule Liquid class Error < ::StandardError attr_accessor :line_number attr_accessor :markup_context def to_s(with_prefix=true) str = "" str << message_prefix if with_prefix str << super() if markup_context str << " " str << markup_context end str end def set_line_number_from_token(token) return unless token.respond_to?(:line_number) return if self.line_number self.line_number = token.line_number end def self.render(e) if e.is_a?(Liquid::Error) e.to_s else "Liquid error: #{e.to_s}" end end private def message_prefix str = "" if is_a?(SyntaxError) str << "Liquid syntax error" else str << "Liquid error" end if line_number str << " (line #{line_number})" end str << ": " str end end class ArgumentError < Error; end class ContextError < Error; end class FileSystemError < Error; end class StandardError < Error; end class SyntaxError < Error; end class StackLevelError < Error; end class TaintedError < Error; end class MemoryError < Error; end end liquid-3.0.6/lib/liquid/context.rb0000644000004100000410000001446712563012415017120 0ustar www-datawww-datamodule Liquid # Context keeps the variable stack and resolves variables, as well as keywords # # context['variable'] = 'testing' # context['variable'] #=> 'testing' # context['true'] #=> true # context['10.2232'] #=> 10.2232 # # context.stack do # context['bob'] = 'bobsen' # end # # context['bob'] #=> nil class Context class Context attr_reader :scopes, :errors, :registers, :environments, :resource_limits attr_accessor :exception_handler def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) @environments = [environments].flatten @scopes = [(outer_scope || {})] @registers = registers @errors = [] @resource_limits = resource_limits || Template.default_resource_limits.dup @resource_limits[:render_score_current] = 0 @resource_limits[:assign_score_current] = 0 @parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) } squash_instance_assigns_with_environments @this_stack_used = false if rethrow_errors self.exception_handler = ->(e) { true } end @interrupts = [] @filters = [] end def increment_used_resources(key, obj) @resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash) obj.length else 1 end end def resource_limits_reached? (@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) || (@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) || (@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] ) end def strainer @strainer ||= Strainer.create(self, @filters) end # Adds filters to this context. # # Note that this does not register the filters with the main Template object. see Template.register_filter # for that def add_filters(filters) filters = [filters].flatten.compact @filters += filters @strainer = nil end # are there any not handled interrupts? def has_interrupt? !@interrupts.empty? end # push an interrupt to the stack. this interrupt is considered not handled. def push_interrupt(e) @interrupts.push(e) end # pop an interrupt from the stack def pop_interrupt @interrupts.pop end def handle_error(e, token=nil) if e.is_a?(Liquid::Error) e.set_line_number_from_token(token) end errors.push(e) raise if exception_handler && exception_handler.call(e) Liquid::Error.render(e) end def invoke(method, *args) strainer.invoke(method, *args).to_liquid end # Push new local scope on the stack. use Context#stack instead def push(new_scope={}) @scopes.unshift(new_scope) raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100 end # Merge a hash of variables in the current local scope def merge(new_scopes) @scopes[0].merge!(new_scopes) end # Pop from the stack. use Context#stack instead def pop raise ContextError if @scopes.size == 1 @scopes.shift end # Pushes a new local scope on the stack, pops it at the end of the block # # Example: # context.stack do # context['var'] = 'hi' # end # # context['var] #=> nil def stack(new_scope=nil) old_stack_used = @this_stack_used if new_scope push(new_scope) @this_stack_used = true else @this_stack_used = false end yield ensure pop if @this_stack_used @this_stack_used = old_stack_used end def clear_instance_assigns @scopes[0] = {} end # Only allow String, Numeric, Hash, Array, Proc, Boolean or Liquid::Drop def []=(key, value) unless @this_stack_used @this_stack_used = true push({}) end @scopes[0][key] = value end # Look up variable, either resolve directly after considering the name. We can directly handle # Strings, digits, floats and booleans (true,false). # If no match is made we lookup the variable in the current scope and # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree. # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions # # Example: # products == empty #=> products.empty? def [](expression) evaluate(@parsed_expression[expression]) end def has_key?(key) self[key] != nil end def evaluate(object) object.respond_to?(:evaluate) ? object.evaluate(self) : object end # Fetches an object starting at the local scope and then moving up the hierachy def find_variable(key) # This was changed from find() to find_index() because this is a very hot # path and find_index() is optimized in MRI to reduce object allocation index = @scopes.find_index { |s| s.has_key?(key) } scope = @scopes[index] if index variable = nil if scope.nil? @environments.each do |e| variable = lookup_and_evaluate(e, key) unless variable.nil? scope = e break end end end scope ||= @environments.last || @scopes.last variable ||= lookup_and_evaluate(scope, key) variable = variable.to_liquid variable.context = self if variable.respond_to?(:context=) return variable end def lookup_and_evaluate(obj, key) if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=) obj[key] = (value.arity == 0) ? value.call : value.call(self) else value end end private def squash_instance_assigns_with_environments @scopes.last.each_key do |k| @environments.each do |env| if env.has_key?(k) scopes.last[k] = lookup_and_evaluate(env, k) break end end end end # squash_instance_assigns_with_environments end # Context end # Liquid liquid-3.0.6/lib/liquid/parser.rb0000644000004100000410000000375312563012415016724 0ustar www-datawww-datamodule Liquid class Parser def initialize(input) l = Lexer.new(input) @tokens = l.tokenize @p = 0 # pointer to current location end def jump(point) @p = point end def consume(type = nil) token = @tokens[@p] if type && token[0] != type raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}" end @p += 1 token[1] end # Only consumes the token if it matches the type # Returns the token's contents if it was consumed # or false otherwise. def consume?(type) token = @tokens[@p] return false unless token && token[0] == type @p += 1 token[1] end # Like consume? Except for an :id token of a certain name def id?(str) token = @tokens[@p] return false unless token && token[0] == :id return false unless token[1] == str @p += 1 token[1] end def look(type, ahead = 0) tok = @tokens[@p + ahead] return false unless tok tok[0] == type end def expression token = @tokens[@p] if token[0] == :id variable_signature elsif [:string, :number].include? token[0] consume elsif token.first == :open_round consume first = expression consume(:dotdot) last = expression consume(:close_round) "(#{first}..#{last})" else raise SyntaxError, "#{token} is not a valid expression" end end def argument str = "" # might be a keyword argument (identifier: expression) if look(:id) && look(:colon, 1) str << consume << consume << ' '.freeze end str << expression str end def variable_signature str = consume(:id) while look(:open_square) str << consume str << expression str << consume(:close_square) end if look(:dot) str << consume str << variable_signature end str end end end liquid-3.0.6/lib/liquid/interrupts.rb0000644000004100000410000000070612563012415017642 0ustar www-datawww-datamodule Liquid # An interrupt is any command that breaks processing of a block (ex: a for loop). class Interrupt attr_reader :message def initialize(message=nil) @message = message || "interrupt".freeze end end # Interrupt that is thrown whenever a {% break %} is called. class BreakInterrupt < Interrupt; end # Interrupt that is thrown whenever a {% continue %} is called. class ContinueInterrupt < Interrupt; end end liquid-3.0.6/lib/liquid/parser_switching.rb0000644000004100000410000000143312563012415020774 0ustar www-datawww-datamodule Liquid module ParserSwitching def parse_with_selected_parser(markup) case @options[:error_mode] || Template.error_mode when :strict then strict_parse_with_error_context(markup) when :lax then lax_parse(markup) when :warn begin return strict_parse_with_error_context(markup) rescue SyntaxError => e e.set_line_number_from_token(markup) @warnings ||= [] @warnings << e return lax_parse(markup) end end end private def strict_parse_with_error_context(markup) strict_parse(markup) rescue SyntaxError => e e.markup_context = markup_context(markup) raise e end def markup_context(markup) "in \"#{markup.strip}\"" end end end liquid-3.0.6/lib/liquid/document.rb0000644000004100000410000000060712563012415017241 0ustar www-datawww-datamodule Liquid class Document < Block def self.parse(tokens, options={}) # we don't need markup to open this block super(nil, nil, tokens, options) end # There isn't a real delimiter def block_delimiter [] end # Document blocks don't need to be terminated since they are not actually opened def assert_missing_delimitation! end end end liquid-3.0.6/lib/liquid/module_ex.rb0000644000004100000410000000316712563012415017410 0ustar www-datawww-data# Copyright 2007 by Domizio Demichelis # This library is free software. It may be used, redistributed and/or modified # under the same terms as Ruby itself # # This extension is used in order to expose the object of the implementing class # to liquid as it were a Drop. It also limits the liquid-callable methods of the instance # to the allowed method passed with the liquid_methods call # Example: # # class SomeClass # liquid_methods :an_allowed_method # # def an_allowed_method # 'this comes from an allowed method' # end # def unallowed_method # 'this will never be an output' # end # end # # if you want to extend the drop to other methods you can defines more methods # in the class ::LiquidDropClass # # class SomeClass::LiquidDropClass # def another_allowed_method # 'and this from another allowed method' # end # end # end # # usage: # @something = SomeClass.new # # template: # {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}} # # output: # 'this comes from an allowed method and this from another allowed method' # # You can also chain associations, by adding the liquid_method call in the # association models. # class Module def liquid_methods(*allowed_methods) drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end" define_method :to_liquid do drop_class.new(self) end drop_class.class_eval do def initialize(object) @object = object end allowed_methods.each do |sym| define_method sym do @object.send sym end end end end end liquid-3.0.6/lib/liquid/standardfilters.rb0000644000004100000410000002213712563012415020616 0ustar www-datawww-datarequire 'cgi' require 'bigdecimal' module Liquid module StandardFilters HTML_ESCAPE = { '&'.freeze => '&'.freeze, '>'.freeze => '>'.freeze, '<'.freeze => '<'.freeze, '"'.freeze => '"'.freeze, "'".freeze => '''.freeze } HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ # Return the size of an array or of an string def size(input) input.respond_to?(:size) ? input.size : 0 end # convert an input string to DOWNCASE def downcase(input) input.to_s.downcase end # convert an input string to UPCASE def upcase(input) input.to_s.upcase end # capitalize words in the input centence def capitalize(input) input.to_s.capitalize end def escape(input) CGI.escapeHTML(input).untaint rescue input end alias_method :h, :escape def escape_once(input) input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) end def url_encode(input) CGI.escape(input) rescue input end def slice(input, offset, length=nil) offset = Integer(offset) length = length ? Integer(length) : 1 if input.is_a?(Array) input.slice(offset, length) || [] else input.to_s.slice(offset, length) || '' end end # Truncate a string down to x characters def truncate(input, length = 50, truncate_string = "...".freeze) if input.nil? then return end l = length.to_i - truncate_string.length l = 0 if l < 0 input.length > length.to_i ? input[0...l] + truncate_string : input end def truncatewords(input, words = 15, truncate_string = "...".freeze) if input.nil? then return end wordlist = input.to_s.split l = words.to_i - 1 l = 0 if l < 0 wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input end # Split input string into an array of substrings separated by given pattern. # # Example: #
{{ post | split '//' | first }}
# def split(input, pattern) input.to_s.split(pattern) end def strip(input) input.to_s.strip end def lstrip(input) input.to_s.lstrip end def rstrip(input) input.to_s.rstrip end def strip_html(input) empty = ''.freeze input.to_s.gsub(//m, empty).gsub(//m, empty).gsub(//m, empty).gsub(/<.*?>/m, empty) end # Remove all newlines from the string def strip_newlines(input) input.to_s.gsub(/\r?\n/, ''.freeze) end # Join elements of the array with certain character between them def join(input, glue = ' '.freeze) InputIterator.new(input).join(glue) end # Sort elements of the array # provide optional property with which to sort an array of hashes or drops def sort(input, property = nil) ary = InputIterator.new(input) if property.nil? ary.sort elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? ary.sort {|a,b| a[property] <=> b[property] } elsif ary.first.respond_to?(property) ary.sort {|a,b| a.send(property) <=> b.send(property) } end end # Remove duplicate elements from an array # provide optional property with which to determine uniqueness def uniq(input, property = nil) ary = InputIterator.new(input) if property.nil? input.uniq elsif input.first.respond_to?(:[]) input.uniq{ |a| a[property] } end end # Reverse the elements of an array def reverse(input) ary = InputIterator.new(input) ary.reverse end # map/collect on a given property def map(input, property) InputIterator.new(input).map do |e| e = e.call if e.is_a?(Proc) if property == "to_liquid".freeze e elsif e.respond_to?(:[]) e[property] end end end # Replace occurrences of a string with another def replace(input, string, replacement = ''.freeze) input.to_s.gsub(string, replacement.to_s) end # Replace the first occurrences of a string with another def replace_first(input, string, replacement = ''.freeze) input.to_s.sub(string, replacement.to_s) end # remove a substring def remove(input, string) input.to_s.gsub(string, ''.freeze) end # remove the first occurrences of a substring def remove_first(input, string) input.to_s.sub(string, ''.freeze) end # add one string to another def append(input, string) input.to_s + string.to_s end # prepend a string to another def prepend(input, string) string.to_s + input.to_s end # Add
tags in front of all newlines in input string def newline_to_br(input) input.to_s.gsub(/\n/, "
\n".freeze) end # Reformat a date using Ruby's core Time#strftime( string ) -> string # # %a - The abbreviated weekday name (``Sun'') # %A - The full weekday name (``Sunday'') # %b - The abbreviated month name (``Jan'') # %B - The full month name (``January'') # %c - The preferred local date and time representation # %d - Day of the month (01..31) # %H - Hour of the day, 24-hour clock (00..23) # %I - Hour of the day, 12-hour clock (01..12) # %j - Day of the year (001..366) # %m - Month of the year (01..12) # %M - Minute of the hour (00..59) # %p - Meridian indicator (``AM'' or ``PM'') # %s - Number of seconds since 1970-01-01 00:00:00 UTC. # %S - Second of the minute (00..60) # %U - Week number of the current year, # starting with the first Sunday as the first # day of the first week (00..53) # %W - Week number of the current year, # starting with the first Monday as the first # day of the first week (00..53) # %w - Day of the week (Sunday is 0, 0..6) # %x - Preferred representation for the date alone, no time # %X - Preferred representation for the time alone, no date # %y - Year without a century (00..99) # %Y - Year with century # %Z - Time zone name # %% - Literal ``%'' character # # See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime def date(input, format) return input if format.to_s.empty? return input unless date = to_date(input) date.strftime(format.to_s) end # Get the first element of the passed in array # # Example: # {{ product.images | first | to_img }} # def first(array) array.first if array.respond_to?(:first) end # Get the last element of the passed in array # # Example: # {{ product.images | last | to_img }} # def last(array) array.last if array.respond_to?(:last) end # addition def plus(input, operand) apply_operation(input, operand, :+) end # subtraction def minus(input, operand) apply_operation(input, operand, :-) end # multiplication def times(input, operand) apply_operation(input, operand, :*) end # division def divided_by(input, operand) apply_operation(input, operand, :/) end def modulo(input, operand) apply_operation(input, operand, :%) end def round(input, n = 0) result = to_number(input).round(to_number(n)) result = result.to_f if result.is_a?(BigDecimal) result = result.to_i if n == 0 result end def ceil(input) to_number(input).ceil.to_i end def floor(input) to_number(input).floor.to_i end def default(input, default_value = "".freeze) is_blank = input.respond_to?(:empty?) ? input.empty? : !input is_blank ? default_value : input end private def to_number(obj) case obj when Float BigDecimal.new(obj.to_s) when Numeric obj when String (obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i else 0 end end def to_date(obj) return obj if obj.respond_to?(:strftime) case obj when 'now'.freeze, 'today'.freeze Time.now when /\A\d+\z/, Integer Time.at(obj.to_i) when String Time.parse(obj) else nil end rescue ::ArgumentError nil end def apply_operation(input, operand, operation) result = to_number(input).send(operation, to_number(operand)) result.is_a?(BigDecimal) ? result.to_f : result end class InputIterator include Enumerable def initialize(input) @input = if input.is_a?(Array) input.flatten elsif input.is_a?(Hash) [input] elsif input.is_a?(Enumerable) input else Array(input) end end def join(glue) to_a.join(glue) end def reverse reverse_each.to_a end def each @input.each do |e| yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) end end end end Template.register_filter(StandardFilters) end liquid-3.0.6/lib/liquid/profiler.rb0000644000004100000410000000767212563012415017256 0ustar www-datawww-datamodule Liquid # Profiler enables support for profiling template rendering to help track down performance issues. # # To enable profiling, pass the profile: true option to Liquid::Template.parse. Then, after # Liquid::Template#render is called, the template object makes available an instance of this # class via the Liquid::Template#profiler method. # # template = Liquid::Template.parse(template_content, profile: true) # output = template.render # profile = template.profiler # # This object contains all profiling information, containing information on what tags were rendered, # where in the templates these tags live, and how long each tag took to render. # # This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times # inside of {% include %} tags. # # profile.each do |node| # # Access to the token itself # node.code # # # Which template and line number of this node. # # If top level, this will be "". # node.partial # node.line_number # # # Render time in seconds of this node # node.render_time # # # If the template used {% include %}, this node will also have children. # node.children.each do |child2| # # ... # end # end # # Profiler also exposes the total time of the template's render in Liquid::Profiler#total_render_time. # # All render times are in seconds. There is a small performance hit when profiling is enabled. # class Profiler include Enumerable class Timing attr_reader :code, :partial, :line_number, :children def initialize(token, partial) @code = token.respond_to?(:raw) ? token.raw : token @partial = partial @line_number = token.respond_to?(:line_number) ? token.line_number : nil @children = [] end def self.start(token, partial) new(token, partial).tap do |t| t.start end end def start @start_time = Time.now end def finish @end_time = Time.now end def render_time @end_time - @start_time end end def self.profile_token_render(token) if Profiler.current_profile && token.respond_to?(:render) Profiler.current_profile.start_token(token) output = yield Profiler.current_profile.end_token(token) output else yield end end def self.profile_children(template_name) if Profiler.current_profile Profiler.current_profile.push_partial(template_name) output = yield Profiler.current_profile.pop_partial output else yield end end def self.current_profile Thread.current[:liquid_profiler] end def initialize @partial_stack = [""] @root_timing = Timing.new("", current_partial) @timing_stack = [@root_timing] @render_start_at = Time.now @render_end_at = @render_start_at end def start Thread.current[:liquid_profiler] = self @render_start_at = Time.now end def stop Thread.current[:liquid_profiler] = nil @render_end_at = Time.now end def total_render_time @render_end_at - @render_start_at end def each(&block) @root_timing.children.each(&block) end def [](idx) @root_timing.children[idx] end def length @root_timing.children.length end def start_token(token) @timing_stack.push(Timing.start(token, current_partial)) end def end_token(token) timing = @timing_stack.pop timing.finish @timing_stack.last.children << timing end def current_partial @partial_stack.last end def push_partial(partial_name) @partial_stack.push(partial_name) end def pop_partial @partial_stack.pop end end end liquid-3.0.6/lib/liquid/version.rb0000644000004100000410000000007012563012415017102 0ustar www-datawww-data# encoding: utf-8 module Liquid VERSION = "3.0.6" end liquid-3.0.6/lib/liquid/expression.rb0000644000004100000410000000144612563012415017624 0ustar www-datawww-datamodule Liquid class Expression LITERALS = { nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil, 'true'.freeze => true, 'false'.freeze => false, 'blank'.freeze => :blank?, 'empty'.freeze => :empty? } def self.parse(markup) if LITERALS.key?(markup) LITERALS[markup] else case markup when /\A'(.*)'\z/m # Single quoted strings $1 when /\A"(.*)"\z/m # Double quoted strings $1 when /\A(-?\d+)\z/ # Integer and floats $1.to_i when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges RangeLookup.parse($1, $2) when /\A(-?\d[\d\.]+)\z/ # Floats $1.to_f else VariableLookup.parse(markup) end end end end end liquid-3.0.6/lib/liquid/block.rb0000644000004100000410000001226312563012415016516 0ustar www-datawww-datamodule Liquid class Block < Tag FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om TAGSTART = "{%".freeze VARSTART = "{{".freeze def blank? @blank end def parse(tokens) @blank = true @nodelist ||= [] @nodelist.clear while token = tokens.shift begin unless token.empty? case when token.start_with?(TAGSTART) if token =~ FullToken # if we found the proper block delimiter just end parsing here and let the outer block # proceed return if block_delimiter == $1 # fetch the tag from registered blocks if tag = Template.tags[$1] markup = token.is_a?(Token) ? token.child($2) : $2 new_tag = tag.parse($1, markup, tokens, @options) new_tag.line_number = token.line_number if token.is_a?(Token) @blank &&= new_tag.blank? @nodelist << new_tag else # this tag is not registered with the system # pass it to the current block for special handling or error reporting unknown_tag($1, $2, tokens) end else raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect)) end when token.start_with?(VARSTART) new_var = create_variable(token) new_var.line_number = token.line_number if token.is_a?(Token) @nodelist << new_var @blank = false else @nodelist << token @blank &&= (token =~ /\A\s*\z/) end end rescue SyntaxError => e e.set_line_number_from_token(token) raise end end # Make sure that it's ok to end parsing in the current block. # Effectively this method will throw an exception unless the current block is # of type Document assert_missing_delimitation! end # warnings of this block and all sub-tags def warnings all_warnings = [] all_warnings.concat(@warnings) if @warnings (nodelist || []).each do |node| all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings) end all_warnings end def unknown_tag(tag, params, tokens) case tag when 'else'.freeze raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze, :block_name => block_name)) when 'end'.freeze raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze, :block_name => block_name, :block_delimiter => block_delimiter)) else raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag)) end end def block_name @tag_name end def block_delimiter @block_delimiter ||= "end#{block_name}" end def create_variable(token) token.scan(ContentOfVariable) do |content| markup = token.is_a?(Token) ? token.child(content.first) : content.first return Variable.new(markup, @options) end raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect)) end def render(context) render_all(@nodelist, context) end protected def assert_missing_delimitation! raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name)) end def render_all(list, context) output = [] context.resource_limits[:render_length_current] = 0 context.resource_limits[:render_score_current] += list.length list.each do |token| # Break out if we have any unhanded interrupts. break if context.has_interrupt? begin # If we get an Interrupt that means the block must stop processing. An # Interrupt is any command that stops block execution such as {% break %} # or {% continue %} if token.is_a? Continue or token.is_a? Break context.push_interrupt(token.interrupt) break end token_output = render_token(token, context) unless token.is_a?(Block) && token.blank? output << token_output end rescue MemoryError => e raise e rescue ::StandardError => e output << (context.handle_error(e, token)) end end output.join end def render_token(token, context) token_output = (token.respond_to?(:render) ? token.render(context) : token) context.increment_used_resources(:render_length_current, token_output) if context.resource_limits_reached? context.resource_limits[:reached] = true raise MemoryError.new("Memory limits exceeded".freeze) end token_output end end end liquid-3.0.6/lib/liquid/tag.rb0000644000004100000410000000126612563012415016200 0ustar www-datawww-datamodule Liquid class Tag attr_accessor :options, :line_number attr_reader :nodelist, :warnings include ParserSwitching class << self def parse(tag_name, markup, tokens, options) tag = new(tag_name, markup, options) tag.parse(tokens) tag end private :new end def initialize(tag_name, markup, options) @tag_name = tag_name @markup = markup @options = options end def parse(tokens) end def raw "#{@tag_name} #{@markup}" end def name self.class.name.downcase end def render(context) ''.freeze end def blank? false end end end liquid-3.0.6/lib/liquid/extensions.rb0000644000004100000410000000117712563012415017625 0ustar www-datawww-datarequire 'time' require 'date' class String # :nodoc: def to_liquid self end end class Array # :nodoc: def to_liquid self end end class Hash # :nodoc: def to_liquid self end end class Numeric # :nodoc: def to_liquid self end end class Time # :nodoc: def to_liquid self end end class DateTime < Date # :nodoc: def to_liquid self end end class Date # :nodoc: def to_liquid self end end class TrueClass def to_liquid # :nodoc: self end end class FalseClass def to_liquid # :nodoc: self end end class NilClass def to_liquid # :nodoc: self end end liquid-3.0.6/lib/liquid/i18n.rb0000644000004100000410000000166612563012415016210 0ustar www-datawww-datarequire 'yaml' module Liquid class I18n DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml") class TranslationError < StandardError end attr_reader :path def initialize(path = DEFAULT_LOCALE) @path = path end def translate(name, vars = {}) interpolate(deep_fetch_translation(name), vars) end alias_method :t, :translate def locale @locale ||= YAML.load_file(@path) end private def interpolate(name, vars) name.gsub(/%\{(\w+)\}/) { # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym] "#{vars[$1.to_sym]}" } end def deep_fetch_translation(name) name.split('.'.freeze).reduce(locale) do |level, cur| level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}" end end end end liquid-3.0.6/lib/liquid/file_system.rb0000644000004100000410000000533612563012415017752 0ustar www-datawww-datamodule Liquid # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag. # # You can implement subclasses that retrieve templates from the database, from the file system using a different # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit. # # You can add additional instance variables, arguments, or methods as needed. # # Example: # # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path) # liquid = Liquid::Template.parse(template) # # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'. class BlankFileSystem # Called by Liquid to retrieve a template file def read_template_file(template_path, context) raise FileSystemError, "This liquid context does not allow includes." end end # This implements an abstract file system which retrieves template files named in a manner similar to Rails partials, # ie. with the template name prefixed with an underscore. The extension ".liquid" is also added. # # For security reasons, template paths are only allowed to contain letters, numbers, and underscore. # # Example: # # file_system = Liquid::LocalFileSystem.new("/some/path") # # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid" # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid" # # Optionally in the second argument you can specify a custom pattern for template filenames. # The Kernel::sprintf format specification is used. # Default pattern is "_%s.liquid". # # Example: # # file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html") # # file_system.full_path("index") # => "/some/path/index.html" # class LocalFileSystem attr_accessor :root def initialize(root, pattern = "_%s.liquid".freeze) @root = root @pattern = pattern end def read_template_file(template_path, context) full_path = full_path(template_path) raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path) File.read(full_path) end def full_path(template_path) raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/ full_path = if template_path.include?('/'.freeze) File.join(root, File.dirname(template_path), @pattern % File.basename(template_path)) else File.join(root, @pattern % template_path) end raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/ full_path end end end liquid-3.0.6/lib/liquid/variable.rb0000644000004100000410000000740012563012415017206 0ustar www-datawww-datamodule Liquid # Holds variables. Variables are only loaded "just in time" # and are not evaluated as part of the render stage # # {{ monkey }} # {{ user.name }} # # Variables can be combined with filters: # # {{ user | link }} # class Variable FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o EasyParse = /\A *(\w+(?:\.\w+)*) *\z/ attr_accessor :filters, :name, :warnings attr_accessor :line_number include ParserSwitching def initialize(markup, options = {}) @markup = markup @name = nil @options = options || {} parse_with_selected_parser(markup) end def raw @markup end def markup_context(markup) "in \"{{#{markup}}}\"" end def lax_parse(markup) @filters = [] if markup =~ /(#{QuotedFragment})(.*)/om name_markup = $1 filter_markup = $2 @name = Expression.parse(name_markup) if filter_markup =~ /#{FilterSeparator}\s*(.*)/om filters = $1.scan(FilterParser) filters.each do |f| if f =~ /\w+/ filtername = Regexp.last_match(0) filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten @filters << parse_filter_expressions(filtername, filterargs) end end end end end def strict_parse(markup) # Very simple valid cases if markup =~ EasyParse @name = Expression.parse($1) @filters = [] return end @filters = [] p = Parser.new(markup) # Could be just filters with no input @name = p.look(:pipe) ? nil : Expression.parse(p.expression) while p.consume?(:pipe) filtername = p.consume(:id) filterargs = p.consume?(:colon) ? parse_filterargs(p) : [] @filters << parse_filter_expressions(filtername, filterargs) end p.consume(:end_of_string) end def parse_filterargs(p) # first argument filterargs = [p.argument] # followed by comma separated others while p.consume?(:comma) filterargs << p.argument end filterargs end def render(context) @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)| filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs) output = context.invoke(filter_name, output, *filter_args) end.tap{ |obj| taint_check(obj) } end private def parse_filter_expressions(filter_name, unparsed_args) filter_args = [] keyword_args = {} unparsed_args.each do |a| if matches = a.match(/\A#{TagAttributes}\z/o) keyword_args[matches[1]] = Expression.parse(matches[2]) else filter_args << Expression.parse(a) end end result = [filter_name, filter_args] result << keyword_args unless keyword_args.empty? result end def evaluate_filter_expressions(context, filter_args, filter_kwargs) parsed_args = filter_args.map{ |expr| context.evaluate(expr) } if filter_kwargs parsed_kwargs = {} filter_kwargs.each do |key, expr| parsed_kwargs[key] = context.evaluate(expr) end parsed_args << parsed_kwargs end parsed_args end def taint_check(obj) if obj.tainted? @markup =~ QuotedFragment name = Regexp.last_match(0) case Template.taint_mode when :warn @warnings ||= [] @warnings << "variable '#{name}' is tainted and was not escaped" when :error raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped" end end end end end liquid-3.0.6/lib/liquid/profiler/0000755000004100000410000000000012563012415016715 5ustar www-datawww-dataliquid-3.0.6/lib/liquid/profiler/hooks.rb0000644000004100000410000000115612563012415020370 0ustar www-datawww-datamodule Liquid class Block < Tag def render_token_with_profiling(token, context) Profiler.profile_token_render(token) do render_token_without_profiling(token, context) end end alias_method :render_token_without_profiling, :render_token alias_method :render_token, :render_token_with_profiling end class Include < Tag def render_with_profiling(context) Profiler.profile_children(@template_name) do render_without_profiling(context) end end alias_method :render_without_profiling, :render alias_method :render, :render_with_profiling end end liquid-3.0.6/lib/liquid/strainer.rb0000644000004100000410000000314212563012415017247 0ustar www-datawww-datarequire 'set' module Liquid # Strainer is the parent class for the filters system. # New filters are mixed into the strainer class which is then instantiated for each liquid template render run. # # The Strainer only allows method calls defined in filters given to it via Strainer.global_filter, # Context#add_filters or Template.register_filter class Strainer #:nodoc: @@global_strainer = Class.new(Strainer) do @filter_methods = Set.new end @@strainer_class_cache = Hash.new do |hash, filters| hash[filters] = Class.new(@@global_strainer) do @filter_methods = @@global_strainer.filter_methods.dup filters.each { |f| add_filter(f) } end end def initialize(context) @context = context end def self.filter_methods @filter_methods end def self.add_filter(filter) raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module) unless self.class.include?(filter) self.send(:include, filter) @filter_methods.merge(filter.public_instance_methods.map(&:to_s)) end end def self.global_filter(filter) @@global_strainer.add_filter(filter) end def self.invokable?(method) @filter_methods.include?(method.to_s) end def self.create(context, filters = []) @@strainer_class_cache[filters].new(context) end def invoke(method, *args) if self.class.invokable?(method) send(method, *args) else args.first end rescue ::ArgumentError => e raise Liquid::ArgumentError.new(e.message) end end end liquid-3.0.6/lib/liquid/range_lookup.rb0000644000004100000410000000106712563012415020111 0ustar www-datawww-datamodule Liquid class RangeLookup def self.parse(start_markup, end_markup) start_obj = Expression.parse(start_markup) end_obj = Expression.parse(end_markup) if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate) new(start_obj, end_obj) else start_obj.to_i..end_obj.to_i end end def initialize(start_obj, end_obj) @start_obj = start_obj @end_obj = end_obj end def evaluate(context) context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i end end end liquid-3.0.6/lib/liquid/token.rb0000644000004100000410000000043412563012415016541 0ustar www-datawww-datamodule Liquid class Token < String attr_reader :line_number def initialize(content, line_number) super(content) @line_number = line_number end def raw "" end def child(string) Token.new(string, @line_number) end end end liquid-3.0.6/lib/liquid/locales/0000755000004100000410000000000012563012415016515 5ustar www-datawww-dataliquid-3.0.6/lib/liquid/locales/en.yml0000644000004100000410000000313312563012415017642 0ustar www-datawww-data--- errors: syntax: assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]" capture: "Syntax Error in 'capture' - Valid syntax: capture [var]" case: "Syntax Error in 'case' - Valid syntax: case [condition]" case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}" case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) " cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]" for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]" for_invalid_in: "For loops require an 'in' clause" for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset" if: "Syntax Error in tag 'if' - Valid syntax: if [expression]" include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]" unknown_tag: "Unknown tag '%{tag}'" invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}" unexpected_else: "%{block_name} tag does not expect else tag" tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}" variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}" 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" liquid-3.0.6/lib/liquid/drop.rb0000644000004100000410000000411612563012415016366 0ustar www-datawww-datarequire 'set' module Liquid # A drop in liquid is a class which allows you to export DOM like things to liquid. # Methods of drops are callable. # The main use for liquid drops is to implement lazy loaded objects. # If you would like to make data available to the web designers which you don't want loaded unless needed then # a drop is a great way to do that. # # Example: # # class ProductDrop < Liquid::Drop # def top_sales # Shop.current.products.find(:all, :order => 'sales', :limit => 10 ) # end # end # # tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' ) # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query. # # Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a # catch all. class Drop attr_writer :context EMPTY_STRING = ''.freeze # Catch all for the method def before_method(method) nil end # called by liquid to invoke a drop def invoke_drop(method_or_key) if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key) send(method_or_key) else before_method(method_or_key) end end def has_key?(name) true end def inspect self.class.to_s end def to_liquid self end def to_s self.class.name end alias :[] :invoke_drop private # Check for method existence without invoking respond_to?, which creates symbols def self.invokable?(method_name) unless @invokable_methods blacklist = Liquid::Drop.public_instance_methods + [:each] if include?(Enumerable) blacklist += Enumerable.public_instance_methods blacklist -= [:sort, :count, :first, :min, :max, :include?] end whitelist = [:to_liquid] + (public_instance_methods - blacklist) @invokable_methods = Set.new(whitelist.map(&:to_s)) end @invokable_methods.include?(method_name.to_s) end end end liquid-3.0.6/lib/liquid/variable_lookup.rb0000644000004100000410000000432712563012415020604 0ustar www-datawww-datamodule Liquid class VariableLookup SQUARE_BRACKETED = /\A\[(.*)\]\z/m COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze] attr_reader :name, :lookups def self.parse(markup) new(markup) end def initialize(markup) lookups = markup.scan(VariableParser) name = lookups.shift if name =~ SQUARE_BRACKETED name = Expression.parse($1) end @name = name @lookups = lookups @command_flags = 0 @lookups.each_index do |i| lookup = lookups[i] if lookup =~ SQUARE_BRACKETED lookups[i] = Expression.parse($1) elsif COMMAND_METHODS.include?(lookup) @command_flags |= 1 << i end end end def evaluate(context) name = context.evaluate(@name) object = context.find_variable(name) @lookups.each_index do |i| key = context.evaluate(@lookups[i]) # If object is a hash- or array-like object we look for the # presence of the key and if its available we return it if object.respond_to?(:[]) && ((object.respond_to?(:has_key?) && object.has_key?(key)) || (object.respond_to?(:fetch) && key.is_a?(Integer))) # if its a proc we will replace the entry with the proc res = context.lookup_and_evaluate(object, key) object = res.to_liquid # Some special cases. If the part wasn't in square brackets and # no key with the same name was found we interpret following calls # as commands and call them on the current object elsif @command_flags & (1 << i) != 0 && object.respond_to?(key) object = object.send(key).to_liquid # No key was present with the desired value and it wasn't one of the directly supported # keywords either. The only thing we got left is to return nil else return nil end # If we are dealing with a drop here we have to object.context = context if object.respond_to?(:context=) end object end def ==(other) self.class == other.class && self.state == other.state end protected def state [@name, @lookups, @command_flags] end end end liquid-3.0.6/lib/liquid/template.rb0000644000004100000410000001563712563012415017247 0ustar www-datawww-datamodule 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 TagRegistry def initialize @tags = {} @cache = {} end def [](tag_name) return nil unless @tags.has_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 private def lookup_class(name) name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) } 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_writer :error_mode # Sets how strict the taint checker should be. # :lax is the default, and ignores the taint flag completely # :warn adds a warning, but does not interrupt the rendering # :error raises an error when tainted output is used attr_writer :taint_mode 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 ||= TagRegistry.new end def error_mode @error_mode || :lax end def taint_mode @taint_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 def default_resource_limits @default_resource_limits ||= {} end # creates a new Template object from liquid source code # To enable profiling, pass in profile: true as an option. # See Liquid::Profiler for more information def parse(source, options = {}) template = Template.new template.parse(source, options) end end def initialize @resource_limits = self.class.default_resource_limits.dup end # Parse source code. # Returns self for easy chaining def parse(source, options = {}) @options = options @profiling = options[:profile] @line_numbers = options[:line_numbers] || @profiling @root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(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 # # if profiling was enabled in Template#parse then the resulting profiling information # will be available via Template#profiler # # 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 ''.freeze if @root.nil? context = case args.first when Liquid::Context c = args.shift if @rethrow_errors c.exception_handler = ->(e) { true } 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 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 if options[:exception_handler] context.exception_handler = options[:exception_handler] 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 = with_profiling do @root.render(context) end 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 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 = calculate_line_numbers(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 def calculate_line_numbers(raw_tokens) return raw_tokens unless @line_numbers current_line = 1 raw_tokens.map do |token| Token.new(token, current_line).tap do current_line += token.count("\n") end end end def with_profiling if @profiling && !@options[:included] @profiler = Profiler.new @profiler.start begin yield ensure @profiler.stop end else yield end end end end liquid-3.0.6/lib/liquid.rb0000644000004100000410000000565412563012415015432 0ustar www-datawww-data# Copyright (c) 2005 Tobias Luetke # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. module Liquid FilterSeparator = /\|/ ArgumentSeparator = ','.freeze FilterArgumentSeparator = ':'.freeze VariableAttributeSeparator = '.'.freeze TagStart = /\{\%/ TagEnd = /\%\}/ VariableSignature = /\(?[\w\-\.\[\]]\)?/ VariableSegment = /[\w\-]/ VariableStart = /\{\{/ VariableEnd = /\}\}/ VariableIncompleteEnd = /\}\}?/ QuotedString = /"[^"]*"|'[^']*'/ QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o AnyStartingTag = /\{\{|\{\%/ PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o singleton_class.send(:attr_accessor, :cache_classes) self.cache_classes = true end require "liquid/version" require 'liquid/lexer' require 'liquid/parser' require 'liquid/i18n' require 'liquid/drop' require 'liquid/extensions' require 'liquid/errors' require 'liquid/interrupts' require 'liquid/strainer' require 'liquid/expression' require 'liquid/context' require 'liquid/parser_switching' require 'liquid/tag' require 'liquid/block' require 'liquid/document' require 'liquid/variable' require 'liquid/variable_lookup' require 'liquid/range_lookup' require 'liquid/file_system' require 'liquid/template' require 'liquid/standardfilters' require 'liquid/condition' require 'liquid/module_ex' require 'liquid/utils' require 'liquid/token' # Load all the tags of the standard library # Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f } require 'liquid/profiler' require 'liquid/profiler/hooks' liquid-3.0.6/metadata.yml0000644000004100000410000001413312563012415015343 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: liquid version: !ruby/object:Gem::Version version: 3.0.6 platform: ruby authors: - Tobias Luetke autorequire: bindir: bin cert_chain: [] date: 2015-07-24 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: minitest requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: email: - tobi@leetsoft.com executables: [] extensions: [] extra_rdoc_files: - History.md - README.md files: - History.md - MIT-LICENSE - README.md - lib/liquid.rb - lib/liquid/block.rb - lib/liquid/block_body.rb - lib/liquid/condition.rb - lib/liquid/context.rb - lib/liquid/document.rb - lib/liquid/drop.rb - lib/liquid/errors.rb - lib/liquid/expression.rb - lib/liquid/extensions.rb - lib/liquid/file_system.rb - lib/liquid/i18n.rb - lib/liquid/interrupts.rb - lib/liquid/lexer.rb - lib/liquid/locales/en.yml - lib/liquid/module_ex.rb - lib/liquid/parser.rb - lib/liquid/parser_switching.rb - lib/liquid/profiler.rb - lib/liquid/profiler/hooks.rb - lib/liquid/range_lookup.rb - lib/liquid/standardfilters.rb - lib/liquid/strainer.rb - lib/liquid/tag.rb - lib/liquid/tags/assign.rb - lib/liquid/tags/break.rb - lib/liquid/tags/capture.rb - lib/liquid/tags/case.rb - lib/liquid/tags/comment.rb - lib/liquid/tags/continue.rb - lib/liquid/tags/cycle.rb - lib/liquid/tags/decrement.rb - lib/liquid/tags/for.rb - lib/liquid/tags/if.rb - lib/liquid/tags/ifchanged.rb - lib/liquid/tags/include.rb - lib/liquid/tags/increment.rb - lib/liquid/tags/raw.rb - lib/liquid/tags/table_row.rb - lib/liquid/tags/unless.rb - lib/liquid/template.rb - lib/liquid/token.rb - lib/liquid/utils.rb - lib/liquid/variable.rb - lib/liquid/variable_lookup.rb - lib/liquid/version.rb - test/fixtures/en_locale.yml - test/integration/assign_test.rb - test/integration/blank_test.rb - test/integration/capture_test.rb - test/integration/context_test.rb - test/integration/drop_test.rb - test/integration/error_handling_test.rb - test/integration/filter_test.rb - test/integration/hash_ordering_test.rb - test/integration/output_test.rb - test/integration/parsing_quirks_test.rb - test/integration/render_profiling_test.rb - test/integration/security_test.rb - test/integration/standard_filter_test.rb - test/integration/tags/break_tag_test.rb - test/integration/tags/continue_tag_test.rb - test/integration/tags/for_tag_test.rb - test/integration/tags/if_else_tag_test.rb - test/integration/tags/include_tag_test.rb - test/integration/tags/increment_tag_test.rb - test/integration/tags/raw_tag_test.rb - test/integration/tags/standard_tag_test.rb - test/integration/tags/statements_test.rb - test/integration/tags/table_row_test.rb - test/integration/tags/unless_else_tag_test.rb - test/integration/template_test.rb - test/integration/variable_test.rb - test/test_helper.rb - test/unit/block_unit_test.rb - test/unit/condition_unit_test.rb - test/unit/context_unit_test.rb - test/unit/file_system_unit_test.rb - test/unit/i18n_unit_test.rb - test/unit/lexer_unit_test.rb - test/unit/module_ex_unit_test.rb - test/unit/parser_unit_test.rb - test/unit/regexp_unit_test.rb - test/unit/strainer_unit_test.rb - test/unit/tag_unit_test.rb - test/unit/tags/case_tag_unit_test.rb - test/unit/tags/for_tag_unit_test.rb - test/unit/tags/if_tag_unit_test.rb - test/unit/template_unit_test.rb - test/unit/tokenizer_unit_test.rb - test/unit/variable_unit_test.rb homepage: http://www.liquidmarkup.org licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.3.7 requirements: [] rubyforge_project: rubygems_version: 2.2.1 signing_key: specification_version: 4 summary: A secure, non-evaling end user template engine with aesthetic markup. test_files: - test/fixtures/en_locale.yml - test/integration/assign_test.rb - test/integration/blank_test.rb - test/integration/capture_test.rb - test/integration/context_test.rb - test/integration/drop_test.rb - test/integration/error_handling_test.rb - test/integration/filter_test.rb - test/integration/hash_ordering_test.rb - test/integration/output_test.rb - test/integration/parsing_quirks_test.rb - test/integration/render_profiling_test.rb - test/integration/security_test.rb - test/integration/standard_filter_test.rb - test/integration/tags/break_tag_test.rb - test/integration/tags/continue_tag_test.rb - test/integration/tags/for_tag_test.rb - test/integration/tags/if_else_tag_test.rb - test/integration/tags/include_tag_test.rb - test/integration/tags/increment_tag_test.rb - test/integration/tags/raw_tag_test.rb - test/integration/tags/standard_tag_test.rb - test/integration/tags/statements_test.rb - test/integration/tags/table_row_test.rb - test/integration/tags/unless_else_tag_test.rb - test/integration/template_test.rb - test/integration/variable_test.rb - test/test_helper.rb - test/unit/block_unit_test.rb - test/unit/condition_unit_test.rb - test/unit/context_unit_test.rb - test/unit/file_system_unit_test.rb - test/unit/i18n_unit_test.rb - test/unit/lexer_unit_test.rb - test/unit/module_ex_unit_test.rb - test/unit/parser_unit_test.rb - test/unit/regexp_unit_test.rb - test/unit/strainer_unit_test.rb - test/unit/tag_unit_test.rb - test/unit/tags/case_tag_unit_test.rb - test/unit/tags/for_tag_unit_test.rb - test/unit/tags/if_tag_unit_test.rb - test/unit/template_unit_test.rb - test/unit/tokenizer_unit_test.rb - test/unit/variable_unit_test.rb liquid-3.0.6/test/0000755000004100000410000000000012563012415014015 5ustar www-datawww-dataliquid-3.0.6/test/fixtures/0000755000004100000410000000000012563012415015666 5ustar www-datawww-dataliquid-3.0.6/test/fixtures/en_locale.yml0000644000004100000410000000037512563012415020337 0ustar www-datawww-data--- simple: "less is more" whatever: "something %{something}" errors: i18n: undefined_interpolation: "undefined key %{key}" unknown_translation: "translation '%{name}' wasn't found" syntax: oops: "something wasn't right" liquid-3.0.6/test/test_helper.rb0000644000004100000410000000467612563012415016675 0ustar www-datawww-data#!/usr/bin/env ruby ENV["MT_NO_EXPECTATIONS"] = "1" require 'minitest/autorun' require 'spy/integration' $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')) require 'liquid.rb' mode = :strict if env_mode = ENV['LIQUID_PARSER_MODE'] puts "-- #{env_mode.upcase} ERROR MODE" mode = env_mode.to_sym end Liquid::Template.error_mode = mode if Minitest.const_defined?('Test') # We're on Minitest 5+. Nothing to do here. else # Minitest 4 doesn't have Minitest::Test yet. Minitest::Test = MiniTest::Unit::TestCase end module Minitest class Test def fixture(name) File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name) end end module Assertions include Liquid def assert_template_result(expected, template, assigns = {}, message = nil) assert_equal expected, Template.parse(template).render!(assigns), message end def assert_template_result_matches(expected, template, assigns = {}, message = nil) return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp assert_match expected, Template.parse(template).render!(assigns), message end def assert_match_syntax_error(match, template, registers = {}) exception = assert_raises(Liquid::SyntaxError) { Template.parse(template).render(assigns) } assert_match match, exception.message end def with_global_filter(*globals) original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer) Liquid::Strainer.class_variable_set(:@@global_strainer, Class.new(Liquid::Strainer) do @filter_methods = Set.new end) Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear globals.each do |global| Liquid::Template.register_filter(global) end yield ensure Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer) end def with_taint_mode(mode) old_mode = Liquid::Template.taint_mode Liquid::Template.taint_mode = mode yield ensure Liquid::Template.taint_mode = old_mode end def with_error_mode(mode) old_mode = Liquid::Template.error_mode Liquid::Template.error_mode = mode yield ensure Liquid::Template.error_mode = old_mode end end end class ThingWithToLiquid def to_liquid 'foobar' end end liquid-3.0.6/test/integration/0000755000004100000410000000000012563012415016340 5ustar www-datawww-dataliquid-3.0.6/test/integration/variable_test.rb0000644000004100000410000000554512563012415021522 0ustar www-datawww-datarequire 'test_helper' class VariableTest < Minitest::Test include Liquid def test_simple_variable template = Template.parse(%|{{test}}|) assert_equal 'worked', template.render!('test' => 'worked') assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully') end def test_variable_render_calls_to_liquid assert_template_result 'foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new end def test_simple_with_whitespaces template = Template.parse(%| {{ test }} |) assert_equal ' worked ', template.render!('test' => 'worked') assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully') end def test_ignore_unknown template = Template.parse(%|{{ test }}|) assert_equal '', template.render! end def test_hash_scoping template = Template.parse(%|{{ test.test }}|) assert_equal 'worked', template.render!('test' => {'test' => 'worked'}) end def test_false_renders_as_false assert_equal 'false', Template.parse("{{ foo }}").render!('foo' => false) assert_equal 'false', Template.parse("{{ false }}").render! end def test_nil_renders_as_empty_string assert_equal '', Template.parse("{{ nil }}").render! assert_equal 'cat', Template.parse("{{ nil | append: 'cat' }}").render! end def test_preset_assigns template = Template.parse(%|{{ test }}|) template.assigns['test'] = 'worked' assert_equal 'worked', template.render! end def test_reuse_parsed_template template = Template.parse(%|{{ greeting }} {{ name }}|) template.assigns['greeting'] = 'Goodbye' assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi') assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi') assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian') assert_equal 'Goodbye Brian', template.render!('name' => 'Brian') assert_equal({'greeting'=>'Goodbye'}, template.assigns) end def test_assigns_not_polluted_from_template template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|) template.assigns['test'] = 'baz' assert_equal 'bazbar', template.render! assert_equal 'bazbar', template.render! assert_equal 'foobar', template.render!('test' => 'foo') assert_equal 'bazbar', template.render! end def test_hash_with_default_proc template = Template.parse(%|Hello {{ test }}|) assigns = Hash.new { |h,k| raise "Unknown variable '#{k}'" } assigns['test'] = 'Tobi' assert_equal 'Hello Tobi', template.render!(assigns) assigns.delete('test') e = assert_raises(RuntimeError) { template.render!(assigns) } assert_equal "Unknown variable 'test'", e.message end def test_multiline_variable assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked') end end liquid-3.0.6/test/integration/error_handling_test.rb0000644000004100000410000001405412563012415022725 0ustar www-datawww-datarequire 'test_helper' class ErrorDrop < Liquid::Drop def standard_error raise Liquid::StandardError, 'standard error' end def argument_error raise Liquid::ArgumentError, 'argument error' end def syntax_error raise Liquid::SyntaxError, 'syntax error' end def exception raise Exception, 'exception' end end class ErrorHandlingTest < Minitest::Test include Liquid def test_templates_parsed_with_line_numbers_renders_them_in_errors template = <<-LIQUID Hello, {{ errors.standard_error }} will raise a standard error. Bla bla test. {{ errors.syntax_error }} will raise a syntax error. This is an argument error: {{ errors.argument_error }} Bla. LIQUID expected = <<-TEXT Hello, Liquid error (line 3): standard error will raise a standard error. Bla bla test. Liquid syntax error (line 7): syntax error will raise a syntax error. This is an argument error: Liquid error (line 9): argument error Bla. TEXT output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new) assert_equal expected, output end def test_standard_error template = Liquid::Template.parse( ' {{ errors.standard_error }} ' ) assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new) assert_equal 1, template.errors.size assert_equal StandardError, template.errors.first.class end def test_syntax template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' ) assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new) assert_equal 1, template.errors.size assert_equal SyntaxError, template.errors.first.class end def test_argument template = Liquid::Template.parse( ' {{ errors.argument_error }} ' ) assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new) assert_equal 1, template.errors.size assert_equal ArgumentError, template.errors.first.class end def test_missing_endtag_parse_time_error assert_raises(Liquid::SyntaxError) do Liquid::Template.parse(' {% for a in b %} ... ') end end def test_unrecognized_operator with_error_mode(:strict) do assert_raises(SyntaxError) do Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ') end end end def test_lax_unrecognized_operator template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax) assert_equal ' Liquid error: Unknown operator =! ', template.render assert_equal 1, template.errors.size assert_equal Liquid::ArgumentError, template.errors.first.class end def test_with_line_numbers_adds_numbers_to_parser_errors err = assert_raises(SyntaxError) do template = Liquid::Template.parse(%q{ foobar {% "cat" | foobar %} bla }, :line_numbers => true ) end assert_match /Liquid syntax error \(line 4\)/, err.message end def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors template = Liquid::Template.parse(%q{ foobar {% if 1 =! 2 %}ok{% endif %} bla }, :error_mode => :warn, :line_numbers => true ) assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'], template.warnings.map(&:message) end def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors err = assert_raises(SyntaxError) do Liquid::Template.parse(%q{ foobar {% if 1 =! 2 %}ok{% endif %} bla }, :error_mode => :strict, :line_numbers => true ) end assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message end def test_syntax_errors_in_nested_blocks_have_correct_line_number err = assert_raises(SyntaxError) do Liquid::Template.parse(%q{ foobar {% if 1 != 2 %} {% foo %} {% endif %} bla }, :line_numbers => true ) end assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message end def test_strict_error_messages err = assert_raises(SyntaxError) do Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict) end assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message err = assert_raises(SyntaxError) do Liquid::Template.parse('{{%%%}}', :error_mode => :strict) end assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message end def test_warnings template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn) assert_equal 3, template.warnings.size assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false) assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false) assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false) assert_equal '', template.render end def test_warning_line_numbers template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true) assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message assert_equal 3, template.warnings.size assert_equal [1,2,3], template.warnings.map(&:line_number) end # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError def test_exceptions_propagate assert_raises Exception do template = Liquid::Template.parse('{{ errors.exception }}') template.render('errors' => ErrorDrop.new) end end end liquid-3.0.6/test/integration/tags/0000755000004100000410000000000012563012415017276 5ustar www-datawww-dataliquid-3.0.6/test/integration/tags/table_row_test.rb0000644000004100000410000000671312563012415022647 0ustar www-datawww-datarequire 'test_helper' class TableRowTest < Minitest::Test include Liquid class ArrayDrop < Liquid::Drop include Enumerable def initialize(array) @array = array end def each(&block) @array.each(&block) end end def test_table_row assert_template_result("\n 1 2 3 \n 4 5 6 \n", '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}', 'numbers' => [1,2,3,4,5,6]) assert_template_result("\n\n", '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}', 'numbers' => []) end def test_table_row_with_different_cols assert_template_result("\n 1 2 3 4 5 \n 6 \n", '{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}', 'numbers' => [1,2,3,4,5,6]) end def test_table_col_counter assert_template_result("\n12\n12\n12\n", '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}', 'numbers' => [1,2,3,4,5,6]) end def test_quoted_fragment assert_template_result("\n 1 2 3 \n 4 5 6 \n", "{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}", 'collections' => {'frontpage' => [1,2,3,4,5,6]}) assert_template_result("\n 1 2 3 \n 4 5 6 \n", "{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}", 'collections' => {'frontpage' => [1,2,3,4,5,6]}) end def test_enumerable_drop assert_template_result("\n 1 2 3 \n 4 5 6 \n", '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}', 'numbers' => ArrayDrop.new([1,2,3,4,5,6])) end def test_offset_and_limit assert_template_result("\n 1 2 3 \n 4 5 6 \n", '{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}', 'numbers' => [0,1,2,3,4,5,6,7]) end end liquid-3.0.6/test/integration/tags/standard_tag_test.rb0000644000004100000410000003246112563012415023323 0ustar www-datawww-datarequire 'test_helper' class StandardTagTest < Minitest::Test include Liquid def test_no_transform assert_template_result('this text should come out of the template without change...', 'this text should come out of the template without change...') assert_template_result('blah','blah') assert_template_result('','') assert_template_result('|,.:','|,.:') assert_template_result('','') text = %|this shouldnt see any transformation either but has multiple lines as you can clearly see here ...| assert_template_result(text,text) end def test_has_a_block_which_does_nothing assert_template_result(%|the comment block should be removed .. right?|, %|the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?|) assert_template_result('','{%comment%}{%endcomment%}') assert_template_result('','{%comment%}{% endcomment %}') assert_template_result('','{% comment %}{%endcomment%}') assert_template_result('','{% comment %}{% endcomment %}') assert_template_result('','{%comment%}comment{%endcomment%}') assert_template_result('','{% comment %}comment{% endcomment %}') assert_template_result('','{% comment %} 1 {% comment %} 2 {% endcomment %} 3 {% endcomment %}') assert_template_result('','{%comment%}{%blabla%}{%endcomment%}') assert_template_result('','{% comment %}{% blabla %}{% endcomment %}') assert_template_result('','{%comment%}{% endif %}{%endcomment%}') assert_template_result('','{% comment %}{% endwhatever %}{% endcomment %}') assert_template_result('','{% comment %}{% raw %} {{%%%%}} }} { {% endcomment %} {% comment {% endraw %} {% endcomment %}') assert_template_result('foobar','foo{%comment%}comment{%endcomment%}bar') assert_template_result('foobar','foo{% comment %}comment{% endcomment %}bar') assert_template_result('foobar','foo{%comment%} comment {%endcomment%}bar') assert_template_result('foobar','foo{% comment %} comment {% endcomment %}bar') assert_template_result('foo bar','foo {%comment%} {%endcomment%} bar') assert_template_result('foo bar','foo {%comment%}comment{%endcomment%} bar') assert_template_result('foo bar','foo {%comment%} comment {%endcomment%} bar') assert_template_result('foobar','foo{%comment%} {%endcomment%}bar') end def test_hyphenated_assign assigns = {'a-b' => '1' } assert_template_result('a-b:1 a-b:2', 'a-b:{{a-b}} {%assign a-b = 2 %}a-b:{{a-b}}', assigns) end def test_assign_with_colon_and_spaces assigns = {'var' => {'a:b c' => {'paged' => '1' }}} assert_template_result('var2: 1', '{%assign var2 = var["a:b c"].paged %}var2: {{var2}}', assigns) end def test_capture assigns = {'var' => 'content' } assert_template_result('content foo content foo ', '{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', assigns) end def test_capture_detects_bad_syntax assert_raises(SyntaxError) do assert_template_result('content foo content foo ', '{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', {'var' => 'content' }) end end def test_case assigns = {'condition' => 2 } assert_template_result(' its 2 ', '{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns) assigns = {'condition' => 1 } assert_template_result(' its 1 ', '{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns) assigns = {'condition' => 3 } assert_template_result('', '{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns) assigns = {'condition' => "string here" } assert_template_result(' hit ', '{% case condition %}{% when "string here" %} hit {% endcase %}', assigns) assigns = {'condition' => "bad string here" } assert_template_result('', '{% case condition %}{% when "string here" %} hit {% endcase %}',\ assigns) end def test_case_with_else assigns = {'condition' => 5 } assert_template_result(' hit ', '{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}', assigns) assigns = {'condition' => 6 } assert_template_result(' else ', '{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}', assigns) assigns = {'condition' => 6 } assert_template_result(' else ', '{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}', assigns) end def test_case_on_size assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => []) assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1]) assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1]) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1]) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1]) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1, 1]) end def test_case_on_size_with_else assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => []) assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1]) assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1]) assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1]) assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1, 1]) assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1, 1, 1]) end def test_case_on_length_with_else assert_template_result('else', '{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) assert_template_result('false', '{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) assert_template_result('true', '{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) assert_template_result('else', '{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) end def test_assign_from_case # Example from the shopify forums code = %q({% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}) template = Liquid::Template.parse(code) assert_equal "menswear", template.render!("collection" => {'handle' => 'menswear-jackets'}) assert_equal "menswear", template.render!("collection" => {'handle' => 'menswear-t-shirts'}) assert_equal "womenswear", template.render!("collection" => {'handle' => 'x'}) assert_equal "womenswear", template.render!("collection" => {'handle' => 'y'}) assert_equal "womenswear", template.render!("collection" => {'handle' => 'z'}) end def test_case_when_or code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 2 }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 3 }) assert_template_result(' its 4 ', code, {'condition' => 4 }) assert_template_result('', code, {'condition' => 5 }) code = '{% case condition %}{% when 1 or "string" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 'string' }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => nil }) assert_template_result('', code, {'condition' => 'something else' }) end def test_case_when_comma code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 2 }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 3 }) assert_template_result(' its 4 ', code, {'condition' => 4 }) assert_template_result('', code, {'condition' => 5 }) code = '{% case condition %}{% when 1, "string", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 'string' }) assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => nil }) assert_template_result('', code, {'condition' => 'something else' }) end def test_assign assert_template_result 'variable', '{% assign a = "variable"%}{{a}}' end def test_assign_unassigned assigns = { 'var' => 'content' } assert_template_result('var2: var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns) end def test_assign_an_empty_string assert_template_result '', '{% assign a = ""%}{{a}}' end def test_assign_is_global assert_template_result 'variable', '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}' end def test_case_detects_bad_syntax assert_raises(SyntaxError) do assert_template_result('', '{% case false %}{% when %}true{% endcase %}', {}) end assert_raises(SyntaxError) do assert_template_result('', '{% case false %}{% huh %}true{% endcase %}', {}) end end def test_cycle assert_template_result('one','{%cycle "one", "two"%}') assert_template_result('one two','{%cycle "one", "two"%} {%cycle "one", "two"%}') assert_template_result(' two','{%cycle "", "two"%} {%cycle "", "two"%}') assert_template_result('one two one','{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}') assert_template_result('text-align: left text-align: right', '{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}') end def test_multiple_cycles assert_template_result('1 2 1 1 2 3 1', '{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}') end def test_multiple_named_cycles assert_template_result('one one two two one one', '{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}') end def test_multiple_named_cycles_with_names_from_context assigns = {"var1" => 1, "var2" => 2 } assert_template_result('one one two two one one', '{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns) end def test_size_of_array assigns = {"array" => [1,2,3,4]} assert_template_result('array has 4 elements', "array has {{ array.size }} elements", assigns) end def test_size_of_hash assigns = {"hash" => {:a => 1, :b => 2, :c=> 3, :d => 4}} assert_template_result('hash has 4 elements', "hash has {{ hash.size }} elements", assigns) end def test_illegal_symbols assert_template_result('', '{% if true == empty %}?{% endif %}', {}) assert_template_result('', '{% if true == null %}?{% endif %}', {}) assert_template_result('', '{% if empty == true %}?{% endif %}', {}) assert_template_result('', '{% if null == true %}?{% endif %}', {}) end def test_ifchanged assigns = {'array' => [ 1, 1, 2, 2, 3, 3] } assert_template_result('123','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns) assigns = {'array' => [ 1, 1, 1, 1] } assert_template_result('1','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns) end def test_multiline_tag assert_template_result '0 1 2 3', "0{%\nfor i in (1..3)\n%} {{\ni\n}}{%\nendfor\n%}" end end # StandardTagTest liquid-3.0.6/test/integration/tags/raw_tag_test.rb0000644000004100000410000000225012563012415022305 0ustar www-datawww-datarequire 'test_helper' class RawTagTest < Minitest::Test include Liquid def test_tag_in_raw assert_template_result '{% comment %} test {% endcomment %}', '{% raw %}{% comment %} test {% endcomment %}{% endraw %}' end def test_output_in_raw assert_template_result '{{ test }}', '{% raw %}{{ test }}{% endraw %}' end def test_open_tag_in_raw assert_template_result ' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}' assert_template_result ' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}' assert_template_result ' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}' assert_template_result ' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}' assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}' assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}' assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}' assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}' end end liquid-3.0.6/test/integration/tags/statements_test.rb0000644000004100000410000000703012563012415023051 0ustar www-datawww-datarequire 'test_helper' class StatementsTest < Minitest::Test include Liquid def test_true_eql_true text = ' {% if true == true %} true {% else %} false {% endif %} ' assert_template_result ' true ', text end def test_true_not_eql_true text = ' {% if true != true %} true {% else %} false {% endif %} ' assert_template_result ' false ', text end def test_true_lq_true text = ' {% if 0 > 0 %} true {% else %} false {% endif %} ' assert_template_result ' false ', text end def test_one_lq_zero text = ' {% if 1 > 0 %} true {% else %} false {% endif %} ' assert_template_result ' true ', text end def test_zero_lq_one text = ' {% if 0 < 1 %} true {% else %} false {% endif %} ' assert_template_result ' true ', text end def test_zero_lq_or_equal_one text = ' {% if 0 <= 0 %} true {% else %} false {% endif %} ' assert_template_result ' true ', text end def test_zero_lq_or_equal_one_involving_nil text = ' {% if null <= 0 %} true {% else %} false {% endif %} ' assert_template_result ' false ', text text = ' {% if 0 <= null %} true {% else %} false {% endif %} ' assert_template_result ' false ', text end def test_zero_lqq_or_equal_one text = ' {% if 0 >= 0 %} true {% else %} false {% endif %} ' assert_template_result ' true ', text end def test_strings text = " {% if 'test' == 'test' %} true {% else %} false {% endif %} " assert_template_result ' true ', text end def test_strings_not_equal text = " {% if 'test' != 'test' %} true {% else %} false {% endif %} " assert_template_result ' false ', text end def test_var_strings_equal text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'var' => 'hello there!' end def test_var_strings_are_not_equal text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'var' => 'hello there!' end def test_var_and_long_string_are_equal text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} " assert_template_result ' true ', text, 'var' => 'hello there!' end def test_var_and_long_string_are_equal_backwards text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} " assert_template_result ' true ', text, 'var' => 'hello there!' end #def test_is_nil # text = %| {% if var != nil %} true {% else %} false {% end %} | # @template.assigns = { 'var' => 'hello there!'} # expected = %| true | # assert_equal expected, @template.parse(text) #end def test_is_collection_empty text = ' {% if array == empty %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'array' => [] end def test_is_not_collection_empty text = ' {% if array == empty %} true {% else %} false {% endif %} ' assert_template_result ' false ', text, 'array' => [1,2,3] end def test_nil text = ' {% if var == nil %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'var' => nil text = ' {% if var == null %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'var' => nil end def test_not_nil text = ' {% if var != nil %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'var' => 1 text = ' {% if var != null %} true {% else %} false {% endif %} ' assert_template_result ' true ', text, 'var' => 1 end end # StatementsTest liquid-3.0.6/test/integration/tags/unless_else_tag_test.rb0000644000004100000410000000233112563012415024035 0ustar www-datawww-datarequire 'test_helper' class UnlessElseTagTest < Minitest::Test include Liquid def test_unless assert_template_result(' ',' {% unless true %} this text should not go into the output {% endunless %} ') assert_template_result(' this text should go into the output ', ' {% unless false %} this text should go into the output {% endunless %} ') assert_template_result(' you rock ?','{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?') end def test_unless_else assert_template_result(' YES ','{% unless true %} NO {% else %} YES {% endunless %}') assert_template_result(' YES ','{% unless false %} YES {% else %} NO {% endunless %}') assert_template_result(' YES ','{% unless "foo" %} NO {% else %} YES {% endunless %}') end def test_unless_in_loop assert_template_result '23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', 'choices' => [1, nil, false] end def test_unless_else_in_loop assert_template_result ' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false] end end # UnlessElseTest liquid-3.0.6/test/integration/tags/include_tag_test.rb0000644000004100000410000001703512563012415023146 0ustar www-datawww-datarequire 'test_helper' class TestFileSystem def read_template_file(template_path, context) case template_path when "product" "Product: {{ product.title }} " when "locale_variables" "Locale: {{echo1}} {{echo2}}" when "variant" "Variant: {{ variant.title }}" when "nested_template" "{% include 'header' %} {% include 'body' %} {% include 'footer' %}" when "body" "body {% include 'body_detail' %}" when "nested_product_template" "Product: {{ nested_product_template.title }} {%include 'details'%} " when "recursively_nested_template" "-{% include 'recursively_nested_template' %}" when "pick_a_source" "from TestFileSystem" when 'assignments' "{% assign foo = 'bar' %}" else template_path end end end class OtherFileSystem def read_template_file(template_path, context) 'from OtherFileSystem' end end class CountingFileSystem attr_reader :count def read_template_file(template_path, context) @count ||= 0 @count += 1 'from CountingFileSystem' end end class CustomInclude < Liquid::Tag Syntax = /(#{Liquid::QuotedFragment}+)(\s+(?:with|for)\s+(#{Liquid::QuotedFragment}+))?/o def initialize(tag_name, markup, tokens) markup =~ Syntax @template_name = $1 super end def parse(tokens) end def render(context) @template_name[1..-2] end end class IncludeTagTest < Minitest::Test include Liquid def setup Liquid::Template.file_system = TestFileSystem.new end def test_include_tag_looks_for_file_system_in_registers_first assert_equal 'from OtherFileSystem', Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => OtherFileSystem.new}) end def test_include_tag_with assert_template_result "Product: Draft 151cm ", "{% include 'product' with products[0] %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] end def test_include_tag_with_default_name assert_template_result "Product: Draft 151cm ", "{% include 'product' %}", "product" => {'title' => 'Draft 151cm'} end def test_include_tag_for assert_template_result "Product: Draft 151cm Product: Element 155cm ", "{% include 'product' for products %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] end def test_include_tag_with_local_variables assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}" end def test_include_tag_with_multiple_local_variables assert_template_result "Locale: test123 test321", "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}" end def test_include_tag_with_multiple_local_variables_from_context assert_template_result "Locale: test123 test321", "{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}", 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'} end def test_included_templates_assigns_variables assert_template_result "bar", "{% include 'assignments' %}{{ foo }}" end def test_nested_include_tag assert_template_result "body body_detail", "{% include 'body' %}" assert_template_result "header body body_detail footer", "{% include 'nested_template' %}" end def test_nested_include_with_variable assert_template_result "Product: Draft 151cm details ", "{% include 'nested_product_template' with product %}", "product" => {"title" => 'Draft 151cm'} assert_template_result "Product: Draft 151cm details Product: Element 155cm details ", "{% include 'nested_product_template' for products %}", "products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}] end def test_recursively_included_template_does_not_produce_endless_loop infinite_file_system = Class.new do def read_template_file(template_path, context) "-{% include 'loop' %}" end end Liquid::Template.file_system = infinite_file_system.new assert_raises(Liquid::StackLevelError, SystemStackError) do Template.parse("{% include 'loop' %}").render! end end def test_backwards_compatability_support_for_overridden_read_template_file infinite_file_system = Class.new do def read_template_file(template_path) # testing only one argument here. "- hi mom" end end Liquid::Template.file_system = infinite_file_system.new Template.parse("{% include 'hi_mom' %}").render! end def test_dynamically_choosen_template assert_template_result "Test123", "{% include template %}", "template" => 'Test123' assert_template_result "Test321", "{% include template %}", "template" => 'Test321' assert_template_result "Product: Draft 151cm ", "{% include template for product %}", "template" => 'product', 'product' => { 'title' => 'Draft 151cm'} end def test_include_tag_caches_second_read_of_same_partial file_system = CountingFileSystem.new assert_equal 'from CountingFileSystemfrom CountingFileSystem', Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system}) assert_equal 1, file_system.count end def test_include_tag_doesnt_cache_partials_across_renders file_system = CountingFileSystem.new assert_equal 'from CountingFileSystem', Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system}) assert_equal 1, file_system.count assert_equal 'from CountingFileSystem', Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system}) assert_equal 2, file_system.count end def test_include_tag_within_if_statement assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}" end def test_custom_include_tag original_tag = Liquid::Template.tags['include'] Liquid::Template.tags['include'] = CustomInclude begin assert_equal "custom_foo", Template.parse("{% include 'custom_foo' %}").render! ensure Liquid::Template.tags['include'] = original_tag end end def test_custom_include_tag_within_if_statement original_tag = Liquid::Template.tags['include'] Liquid::Template.tags['include'] = CustomInclude begin assert_equal "custom_foo_if_true", Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render! ensure Liquid::Template.tags['include'] = original_tag end end def test_does_not_add_error_in_strict_mode_for_missing_variable Liquid::Template.file_system = TestFileSystem.new a = Liquid::Template.parse(' {% include "nested_template" %}') a.render! assert_empty a.errors end def test_passing_options_to_included_templates assert_raises(Liquid::SyntaxError) do Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}') end with_error_mode(:lax) do assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}') end assert_raises(Liquid::SyntaxError) do Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}') end with_error_mode(:lax) do assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}') end end end # IncludeTagTest liquid-3.0.6/test/integration/tags/continue_tag_test.rb0000644000004100000410000000051412563012415023341 0ustar www-datawww-datarequire 'test_helper' class ContinueTagTest < Minitest::Test include Liquid # tests that no weird errors are raised if continue is called outside of a # block def test_continue_with_no_block assigns = {} markup = '{% continue %}' expected = '' assert_template_result(expected, markup, assigns) end end liquid-3.0.6/test/integration/tags/if_else_tag_test.rb0000644000004100000410000002215612563012415023131 0ustar www-datawww-datarequire 'test_helper' class IfElseTagTest < Minitest::Test include Liquid def test_if assert_template_result(' ',' {% if false %} this text should not go into the output {% endif %} ') assert_template_result(' this text should go into the output ', ' {% if true %} this text should go into the output {% endif %} ') assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?') end def test_literal_comparisons assert_template_result(' NO ','{% assign v = false %}{% if v %} YES {% else %} NO {% endif %}') assert_template_result(' YES ','{% assign v = nil %}{% if v == nil %} YES {% else %} NO {% endif %}') end def test_if_else assert_template_result(' YES ','{% if false %} NO {% else %} YES {% endif %}') assert_template_result(' YES ','{% if true %} YES {% else %} NO {% endif %}') assert_template_result(' YES ','{% if "foo" %} YES {% else %} NO {% endif %}') end def test_if_boolean assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => true) end def test_if_or assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true) assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false) assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true) assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false) assert_template_result(' YES ','{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true) assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false) end def test_if_or_with_operators assert_template_result(' YES ','{% if a == true or b == true %} YES {% endif %}', 'a' => true, 'b' => true) assert_template_result(' YES ','{% if a == true or b == false %} YES {% endif %}', 'a' => true, 'b' => true) assert_template_result('','{% if a == false or b == false %} YES {% endif %}', 'a' => true, 'b' => true) end def test_comparison_of_strings_containing_and_or_or awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar" assigns = {'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true} assert_template_result(' YES ',"{% if #{awful_markup} %} YES {% endif %}", assigns) end def test_comparison_of_expressions_starting_with_and_or_or assigns = {'order' => {'items_count' => 0}, 'android' => {'name' => 'Roy'}} assert_template_result( "YES", "{% if android.name == 'Roy' %}YES{% endif %}", assigns) assert_template_result( "YES", "{% if order.items_count == 0 %}YES{% endif %}", assigns) end def test_if_and assert_template_result(' YES ','{% if true and true %} YES {% endif %}') assert_template_result('','{% if false and true %} YES {% endif %}') assert_template_result('','{% if false and true %} YES {% endif %}') end def test_hash_miss_generates_false assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {}) end def test_if_from_variable assert_template_result('','{% if var %} NO {% endif %}', 'var' => false) assert_template_result('','{% if var %} NO {% endif %}', 'var' => nil) assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {'bar' => false}) assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {}) assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => nil) assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => true) assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => "text") assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => true) assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => 1) assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => {}) assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => []) assert_template_result(' YES ','{% if "foo" %} YES {% endif %}') assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => true}) assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => "text"}) assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => 1 }) assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => {} }) assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => [] }) assert_template_result(' YES ','{% if var %} NO {% else %} YES {% endif %}', 'var' => false) assert_template_result(' YES ','{% if var %} NO {% else %} YES {% endif %}', 'var' => nil) assert_template_result(' YES ','{% if var %} YES {% else %} NO {% endif %}', 'var' => true) assert_template_result(' YES ','{% if "foo" %} YES {% else %} NO {% endif %}', 'var' => "text") assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {'bar' => false}) assert_template_result(' YES ','{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => {'bar' => true}) assert_template_result(' YES ','{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => {'bar' => "text"}) assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {'notbar' => true}) assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {}) assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'notfoo' => {'bar' => true}) end def test_nested_if assert_template_result('', '{% if false %}{% if false %} NO {% endif %}{% endif %}') assert_template_result('', '{% if false %}{% if true %} NO {% endif %}{% endif %}') assert_template_result('', '{% if true %}{% if false %} NO {% endif %}{% endif %}') assert_template_result(' YES ', '{% if true %}{% if true %} YES {% endif %}{% endif %}') assert_template_result(' YES ', '{% if true %}{% if true %} YES {% else %} NO {% endif %}{% else %} NO {% endif %}') assert_template_result(' YES ', '{% if true %}{% if false %} NO {% else %} YES {% endif %}{% else %} NO {% endif %}') assert_template_result(' YES ', '{% if false %}{% if true %} NO {% else %} NONO {% endif %}{% else %} YES {% endif %}') end def test_comparisons_on_null assert_template_result('','{% if null < 10 %} NO {% endif %}') assert_template_result('','{% if null <= 10 %} NO {% endif %}') assert_template_result('','{% if null >= 10 %} NO {% endif %}') assert_template_result('','{% if null > 10 %} NO {% endif %}') assert_template_result('','{% if 10 < null %} NO {% endif %}') assert_template_result('','{% if 10 <= null %} NO {% endif %}') assert_template_result('','{% if 10 >= null %} NO {% endif %}') assert_template_result('','{% if 10 > null %} NO {% endif %}') end def test_else_if assert_template_result('0','{% if 0 == 0 %}0{% elsif 1 == 1%}1{% else %}2{% endif %}') assert_template_result('1','{% if 0 != 0 %}0{% elsif 1 == 1%}1{% else %}2{% endif %}') assert_template_result('2','{% if 0 != 0 %}0{% elsif 1 != 1%}1{% else %}2{% endif %}') assert_template_result('elsif','{% if false %}if{% elsif true %}elsif{% endif %}') end def test_syntax_error_no_variable assert_raises(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')} end def test_syntax_error_no_expression assert_raises(SyntaxError) { assert_template_result('', '{% if %}') } end def test_if_with_custom_condition original_op = Condition.operators['contains'] Condition.operators['contains'] = :[] assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %})) assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %})) ensure Condition.operators['contains'] = original_op end def test_operators_are_ignored_unless_isolated original_op = Condition.operators['contains'] Condition.operators['contains'] = :[] assert_template_result('yes', %({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %})) ensure Condition.operators['contains'] = original_op end def test_operators_are_whitelisted assert_raises(SyntaxError) do assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %})) end end def test_multiple_conditions tpl = "{% if a or b and c %}true{% else %}false{% endif %}" tests = { [true, true, true] => true, [true, true, false] => true, [true, false, true] => true, [true, false, false] => true, [false, true, true] => true, [false, true, false] => false, [false, false, true] => false, [false, false, false] => false, } tests.each do |vals, expected| a, b, c = vals assigns = { 'a' => a, 'b' => b, 'c' => c } assert_template_result expected.to_s, tpl, assigns, assigns.to_s end end end liquid-3.0.6/test/integration/tags/increment_tag_test.rb0000644000004100000410000000165512563012415023510 0ustar www-datawww-datarequire 'test_helper' class IncrementTagTest < Minitest::Test include Liquid def test_inc assert_template_result('0','{%increment port %}', {}) assert_template_result('0 1','{%increment port %} {%increment port%}', {}) assert_template_result('0 0 1 2 1', '{%increment port %} {%increment starboard%} ' + '{%increment port %} {%increment port%} ' + '{%increment starboard %}', {}) end def test_dec assert_template_result('9','{%decrement port %}', { 'port' => 10}) assert_template_result('-1 -2','{%decrement port %} {%decrement port%}', {}) assert_template_result('1 5 2 2 5', '{%increment port %} {%increment starboard%} ' + '{%increment port %} {%decrement port%} ' + '{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 }) end end liquid-3.0.6/test/integration/tags/for_tag_test.rb0000644000004100000410000003153712563012415022314 0ustar www-datawww-datarequire 'test_helper' class ThingWithValue < Liquid::Drop def value 3 end end class ForTagTest < Minitest::Test include Liquid def test_for assert_template_result(' yo yo yo yo ','{%for item in array%} yo {%endfor%}','array' => [1,2,3,4]) assert_template_result('yoyo','{%for item in array%}yo{%endfor%}','array' => [1,2]) assert_template_result(' yo ','{%for item in array%} yo {%endfor%}','array' => [1]) assert_template_result('','{%for item in array%}{%endfor%}','array' => [1,2]) expected = < [1,2,3]) end def test_for_reversed assigns = {'array' => [ 1, 2, 3] } assert_template_result('321','{%for item in array reversed %}{{item}}{%endfor%}',assigns) end def test_for_with_range assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}') end def test_for_with_variable_range assert_template_result(' 1 2 3 ','{%for item in (1..foobar) %} {{item}} {%endfor%}', "foobar" => 3) end def test_for_with_hash_value_range foobar = { "value" => 3 } assert_template_result(' 1 2 3 ','{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar) end def test_for_with_drop_value_range foobar = ThingWithValue.new assert_template_result(' 1 2 3 ','{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar) end def test_for_with_variable assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3]) assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3]) assert_template_result('123','{% for item in array %}{{item}}{% endfor %}','array' => [1,2,3]) assert_template_result('abcd','{%for item in array%}{{item}}{%endfor%}','array' => ['a','b','c','d']) assert_template_result('a b c','{%for item in array%}{{item}}{%endfor%}','array' => ['a',' ','b',' ','c']) assert_template_result('abc','{%for item in array%}{{item}}{%endfor%}','array' => ['a','','b','','c']) end def test_for_helpers assigns = {'array' => [1,2,3] } assert_template_result(' 1/3 2/3 3/3 ', '{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}', assigns) assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns) assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns) assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns) assert_template_result(' 3 2 1 ', '{%for item in array%} {{forloop.rindex}} {%endfor%}', assigns) assert_template_result(' true false false ', '{%for item in array%} {{forloop.first}} {%endfor%}', assigns) assert_template_result(' false false true ', '{%for item in array%} {{forloop.last}} {%endfor%}', assigns) end def test_for_and_if assigns = {'array' => [1,2,3] } assert_template_result('+--', '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}', assigns) end def test_for_else assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[1,2,3]) assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[]) assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>nil) end def test_limiting assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]} assert_template_result('12', '{%for i in array limit:2 %}{{ i }}{%endfor%}', assigns) assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns) assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns) assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns) end def test_dynamic_variable_limiting assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]} assigns['limit'] = 2 assigns['offset'] = 2 assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns) end def test_nested_for assigns = {'array' => [[1,2],[3,4],[5,6]] } assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns) end def test_offset_only assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]} assert_template_result('890', '{%for i in array offset:7 %}{{ i }}{%endfor%}', assigns) end def test_pause_resume assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}} markup = <<-MKUP {%for i in array.items limit: 3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%} MKUP expected = <<-XPCTD 123 next 456 next 789 XPCTD assert_template_result(expected,markup,assigns) end def test_pause_resume_limit assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}} markup = <<-MKUP {%for i in array.items limit:3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%} MKUP expected = <<-XPCTD 123 next 456 next 7 XPCTD assert_template_result(expected,markup,assigns) end def test_pause_resume_BIG_limit assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}} markup = <<-MKUP {%for i in array.items limit:3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%} MKUP expected = <<-XPCTD 123 next 456 next 7890 XPCTD assert_template_result(expected,markup,assigns) end def test_pause_resume_BIG_offset assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}} markup = %q({%for i in array.items limit:3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%} next {%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%}) expected = %q(123 next 456 next ) assert_template_result(expected,markup,assigns) end def test_for_with_break assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,10]}} markup = '{% for i in array.items %}{% break %}{% endfor %}' expected = "" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}' expected = "1" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}' expected = "" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}' expected = "1234" assert_template_result(expected,markup,assigns) # tests to ensure it only breaks out of the local for loop # and not all of them. assigns = {'array' => [[1,2],[3,4],[5,6]] } markup = '{% for item in array %}' + '{% for i in item %}' + '{% if i == 1 %}' + '{% break %}' + '{% endif %}' + '{{ i }}' + '{% endfor %}' + '{% endfor %}' expected = '3456' assert_template_result(expected, markup, assigns) # test break does nothing when unreached assigns = {'array' => {'items' => [1,2,3,4,5]}} markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}' expected = '12345' assert_template_result(expected, markup, assigns) end def test_for_with_continue assigns = {'array' => {'items' => [1,2,3,4,5]}} markup = '{% for i in array.items %}{% continue %}{% endfor %}' expected = "" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}' expected = "12345" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}' expected = "" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}' expected = "123" assert_template_result(expected,markup,assigns) markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}' expected = "1245" assert_template_result(expected,markup,assigns) # tests to ensure it only continues the local for loop and not all of them. assigns = {'array' => [[1,2],[3,4],[5,6]] } markup = '{% for item in array %}' + '{% for i in item %}' + '{% if i == 1 %}' + '{% continue %}' + '{% endif %}' + '{{ i }}' + '{% endfor %}' + '{% endfor %}' expected = '23456' assert_template_result(expected, markup, assigns) # test continue does nothing when unreached assigns = {'array' => {'items' => [1,2,3,4,5]}} markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}' expected = '12345' assert_template_result(expected, markup, assigns) end def test_for_tag_string # ruby 1.8.7 "String".each => Enumerator with single "String" element. # ruby 1.9.3 no longer supports .each on String though we mimic # the functionality for backwards compatibility assert_template_result('test string', '{%for val in string%}{{val}}{%endfor%}', 'string' => "test string") assert_template_result('test string', '{%for val in string limit:1%}{{val}}{%endfor%}', 'string' => "test string") assert_template_result('val-string-1-1-0-1-0-true-true-test string', '{%for val in string%}' + '{{forloop.name}}-' + '{{forloop.index}}-' + '{{forloop.length}}-' + '{{forloop.index0}}-' + '{{forloop.rindex}}-' + '{{forloop.rindex0}}-' + '{{forloop.first}}-' + '{{forloop.last}}-' + '{{val}}{%endfor%}', 'string' => "test string") end def test_blank_string_not_iterable assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '') end def test_bad_variable_naming_in_for_loop assert_raises(Liquid::SyntaxError) do Liquid::Template.parse('{% for a/b in x %}{% endfor %}') end end def test_spacing_with_variable_naming_in_for_loop expected = '12345' template = '{% for item in items %}{{item}}{% endfor %}' assigns = {'items' => [1,2,3,4,5]} assert_template_result(expected, template, assigns) end class LoaderDrop < Liquid::Drop attr_accessor :each_called, :load_slice_called def initialize(data) @data = data end def each @each_called = true @data.each { |el| yield el } end def load_slice(from, to) @load_slice_called = true @data[(from..to-1)] end end def test_iterate_with_each_when_no_limit_applied loader = LoaderDrop.new([1,2,3,4,5]) assigns = {'items' => loader} expected = '12345' template = '{% for item in items %}{{item}}{% endfor %}' assert_template_result(expected, template, assigns) assert loader.each_called assert !loader.load_slice_called end def test_iterate_with_load_slice_when_limit_applied loader = LoaderDrop.new([1,2,3,4,5]) assigns = {'items' => loader} expected = '1' template = '{% for item in items limit:1 %}{{item}}{% endfor %}' assert_template_result(expected, template, assigns) assert !loader.each_called assert loader.load_slice_called end def test_iterate_with_load_slice_when_limit_and_offset_applied loader = LoaderDrop.new([1,2,3,4,5]) assigns = {'items' => loader} expected = '34' template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}' assert_template_result(expected, template, assigns) assert !loader.each_called assert loader.load_slice_called end def test_iterate_with_load_slice_returns_same_results_as_without loader = LoaderDrop.new([1,2,3,4,5]) loader_assigns = {'items' => loader} array_assigns = {'items' => [1,2,3,4,5]} expected = '34' template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}' assert_template_result(expected, template, loader_assigns) assert_template_result(expected, template, array_assigns) end end liquid-3.0.6/test/integration/tags/break_tag_test.rb0000644000004100000410000000051012563012415022575 0ustar www-datawww-datarequire 'test_helper' class BreakTagTest < Minitest::Test include Liquid # tests that no weird errors are raised if break is called outside of a # block def test_break_with_no_block assigns = {'i' => 1} markup = '{% break %}' expected = '' assert_template_result(expected, markup, assigns) end end liquid-3.0.6/test/integration/context_test.rb0000644000004100000410000000134612563012415021414 0ustar www-datawww-datarequire 'test_helper' class ContextTest < Minitest::Test include Liquid def test_override_global_filter global = Module.new do def notice(output) "Global #{output}" end end local = Module.new do def notice(output) "Local #{output}" end end with_global_filter(global) do assert_equal 'Global test', Template.parse("{{'test' | notice }}").render! assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local]) end end def test_has_key_will_not_add_an_error_for_missing_keys with_error_mode :strict do context = Context.new context.has_key?('unknown') assert_empty context.errors end end end liquid-3.0.6/test/integration/security_test.rb0000644000004100000410000000327312563012415021600 0ustar www-datawww-datarequire 'test_helper' module SecurityFilter def add_one(input) "#{input} + 1" end end class SecurityTest < Minitest::Test include Liquid def test_no_instance_eval text = %( {{ '1+1' | instance_eval }} ) expected = %| 1+1 | assert_equal expected, Template.parse(text).render!(@assigns) end def test_no_existing_instance_eval text = %( {{ '1+1' | __instance_eval__ }} ) expected = %| 1+1 | assert_equal expected, Template.parse(text).render!(@assigns) end def test_no_instance_eval_after_mixing_in_new_filter text = %( {{ '1+1' | instance_eval }} ) expected = %| 1+1 | assert_equal expected, Template.parse(text).render!(@assigns) end def test_no_instance_eval_later_in_chain text = %( {{ '1+1' | add_one | instance_eval }} ) expected = %| 1+1 + 1 | assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter) end def test_does_not_add_filters_to_symbol_table current_symbols = Symbol.all_symbols test = %( {{ "some_string" | a_bad_filter }} ) template = Template.parse(test) assert_equal [], (Symbol.all_symbols - current_symbols) template.render! assert_equal [], (Symbol.all_symbols - current_symbols) end def test_does_not_add_drop_methods_to_symbol_table current_symbols = Symbol.all_symbols assigns = { 'drop' => Drop.new } assert_equal "", Template.parse("{{ drop.custom_method_1 }}", assigns).render! assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render! assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render! assert_equal [], (Symbol.all_symbols - current_symbols) end end # SecurityTest liquid-3.0.6/test/integration/capture_test.rb0000644000004100000410000000270712563012415021375 0ustar www-datawww-datarequire 'test_helper' class CaptureTest < Minitest::Test include Liquid def test_captures_block_content_in_variable assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {}) end def test_capture_with_hyphen_in_variable_name template_source = <<-END_TEMPLATE {% capture this-thing %}Print this-thing{% endcapture %} {{ this-thing }} END_TEMPLATE template = Template.parse(template_source) rendered = template.render! assert_equal "Print this-thing", rendered.strip end def test_capture_to_variable_from_outer_scope_if_existing template_source = <<-END_TEMPLATE {% assign var = '' %} {% if true %} {% capture var %}first-block-string{% endcapture %} {% endif %} {% if true %} {% capture var %}test-string{% endcapture %} {% endif %} {{var}} END_TEMPLATE template = Template.parse(template_source) rendered = template.render! assert_equal "test-string", rendered.gsub(/\s/, '') end def test_assigning_from_capture template_source = <<-END_TEMPLATE {% assign first = '' %} {% assign second = '' %} {% for number in (1..3) %} {% capture first %}{{number}}{% endcapture %} {% assign second = first %} {% endfor %} {{ first }}-{{ second }} END_TEMPLATE template = Template.parse(template_source) rendered = template.render! assert_equal "3-3", rendered.gsub(/\s/, '') end end # CaptureTest liquid-3.0.6/test/integration/parsing_quirks_test.rb0000644000004100000410000000651512563012415022774 0ustar www-datawww-datarequire 'test_helper' class ParsingQuirksTest < Minitest::Test include Liquid def test_parsing_css text = " div { font-weight: bold; } " assert_equal text, Template.parse(text).render! end def test_raise_on_single_close_bracet assert_raises(SyntaxError) do Template.parse("text {{method} oh nos!") end end def test_raise_on_label_and_no_close_bracets assert_raises(SyntaxError) do Template.parse("TEST {{ ") end end def test_raise_on_label_and_no_close_bracets_percent assert_raises(SyntaxError) do Template.parse("TEST {% ") end end def test_error_on_empty_filter assert Template.parse("{{test}}") assert Template.parse("{{|test}}") with_error_mode(:strict) do assert_raises(SyntaxError) do Template.parse("{{test |a|b|}}") end end end def test_meaningless_parens_error with_error_mode(:strict) do assert_raises(SyntaxError) do markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false" Template.parse("{% if #{markup} %} YES {% endif %}") end end end def test_unexpected_characters_syntax_error with_error_mode(:strict) do assert_raises(SyntaxError) do markup = "true && false" Template.parse("{% if #{markup} %} YES {% endif %}") end assert_raises(SyntaxError) do markup = "false || true" Template.parse("{% if #{markup} %} YES {% endif %}") end end end def test_no_error_on_lax_empty_filter assert Template.parse("{{test |a|b|}}", :error_mode => :lax) assert Template.parse("{{test}}", :error_mode => :lax) assert Template.parse("{{|test|}}", :error_mode => :lax) end def test_meaningless_parens_lax with_error_mode(:lax) do assigns = {'b' => 'bar', 'c' => 'baz'} markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false" assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns) end end def test_unexpected_characters_silently_eat_logic_lax with_error_mode(:lax) do markup = "true && false" assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}") markup = "false || true" assert_template_result('',"{% if #{markup} %} YES {% endif %}") end end def test_raise_on_invalid_tag_delimiter assert_raises(Liquid::SyntaxError) do Template.new.parse('{% end %}') end end def test_unanchored_filter_arguments with_error_mode(:lax) do assert_template_result('hi',"{{ 'hi there' | split$$$:' ' | first }}") assert_template_result('x', "{{ 'X' | downcase) }}") # After the messed up quotes a filter without parameters (reverse) should work # but one with parameters (remove) shouldn't be detected. assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}") assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}") end end def test_invalid_variables_work with_error_mode(:lax) do assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}") assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}") end end def test_extra_dots_in_ranges with_error_mode(:lax) do assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}") end end end # ParsingQuirksTest liquid-3.0.6/test/integration/drop_test.rb0000644000004100000410000002363412563012415020700 0ustar www-datawww-datarequire 'test_helper' class ContextDrop < Liquid::Drop def scopes @context.scopes.size end def scopes_as_array (1..@context.scopes.size).to_a end def loop_pos @context['forloop.index'] end def before_method(method) return @context[method] end end class ProductDrop < Liquid::Drop class TextDrop < Liquid::Drop def array ['text1', 'text2'] end def text 'text1' end end class CatchallDrop < Liquid::Drop def before_method(method) return 'method: ' << method.to_s end end def texts TextDrop.new end def catchall CatchallDrop.new end def context ContextDrop.new end def user_input "foo".taint end protected def callmenot "protected" end end class EnumerableDrop < Liquid::Drop def before_method(method) method end def size 3 end def first 1 end def count 3 end def min 1 end def max 3 end def each yield 1 yield 2 yield 3 end end class RealEnumerableDrop < Liquid::Drop include Enumerable def before_method(method) method end def each yield 1 yield 2 yield 3 end end class DropsTest < Minitest::Test include Liquid def test_product_drop tpl = Liquid::Template.parse(' ') assert_equal ' ', tpl.render!('product' => ProductDrop.new) end def test_rendering_raises_on_tainted_attr with_taint_mode(:error) do tpl = Liquid::Template.parse('{{ product.user_input }}') assert_raises TaintedError do tpl.render!('product' => ProductDrop.new) end end end def test_rendering_warns_on_tainted_attr with_taint_mode(:warn) do tpl = Liquid::Template.parse('{{ product.user_input }}') tpl.render!('product' => ProductDrop.new) assert_match /tainted/, tpl.warnings.first end end def test_rendering_doesnt_raise_on_escaped_tainted_attr with_taint_mode(:error) do tpl = Liquid::Template.parse('{{ product.user_input | escape }}') tpl.render!('product' => ProductDrop.new) end end def test_drop_does_only_respond_to_whitelisted_methods assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new) assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new) assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new) assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new) assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new) assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new) end def test_drops_respond_to_to_liquid assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new) assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new) end def test_text_drop output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render!('product' => ProductDrop.new) assert_equal ' text1 ', output end def test_unknown_method output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render!('product' => ProductDrop.new) assert_equal ' method: unknown ', output end def test_integer_argument_drop output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render!('product' => ProductDrop.new) assert_equal ' method: 8 ', output end def test_text_array_drop output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render!('product' => ProductDrop.new) assert_equal ' text1 text2 ', output end def test_context_drop output = Liquid::Template.parse( ' {{ context.bar }} ' ).render!('context' => ContextDrop.new, 'bar' => "carrot") assert_equal ' carrot ', output end def test_nested_context_drop output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render!('product' => ProductDrop.new, 'foo' => "monkey") assert_equal ' monkey ', output end def test_protected output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render!('product' => ProductDrop.new) assert_equal ' ', output end def test_object_methods_not_allowed [:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method| output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new) assert_equal ' ', output end end def test_scope assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render!('context' => ContextDrop.new) assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) end def test_scope_though_proc assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }) assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1]) assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1]) end def test_scope_with_assigns assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render!('context' => ContextDrop.new) assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render!('context' => ContextDrop.new) assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render!('context' => ContextDrop.new) end def test_scope_from_tags assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) end def test_access_context_from_drop assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1,2,3]) end def test_enumerable_drop assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new) end def test_enumerable_drop_size assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render!('collection' => EnumerableDrop.new) end def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names ["select", "each", "map", "cycle"].each do |method| assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new) assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new) assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new) assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new) end end def test_some_enumerable_methods_still_get_invoked [ :count, :max ].each do |method| assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new) end assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new) [ :min, :first ].each do |method| assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new) end end def test_empty_string_value_access assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '') end def test_nil_value_access assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil) end def test_default_to_s_on_drops assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new) assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new) end end # DropsTest liquid-3.0.6/test/integration/standard_filter_test.rb0000644000004100000410000003234712563012415023102 0ustar www-datawww-data# encoding: utf-8 require 'test_helper' class Filters include Liquid::StandardFilters end class TestThing attr_reader :foo def initialize @foo = 0 end def to_s "woot: #{@foo}" end def [](whatever) to_s end def to_liquid @foo += 1 self end end class TestDrop < Liquid::Drop def test "testfoo" end end class TestEnumerable < Liquid::Drop include Enumerable def each(&block) [ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block) end end class StandardFiltersTest < Minitest::Test include Liquid def setup @filters = Filters.new end def test_size assert_equal 3, @filters.size([1,2,3]) assert_equal 0, @filters.size([]) assert_equal 0, @filters.size(nil) end def test_downcase assert_equal 'testing', @filters.downcase("Testing") assert_equal '', @filters.downcase(nil) end def test_upcase assert_equal 'TESTING', @filters.upcase("Testing") assert_equal '', @filters.upcase(nil) end def test_slice assert_equal 'oob', @filters.slice('foobar', 1, 3) assert_equal 'oobar', @filters.slice('foobar', 1, 1000) assert_equal '', @filters.slice('foobar', 1, 0) assert_equal 'o', @filters.slice('foobar', 1, 1) assert_equal 'bar', @filters.slice('foobar', 3, 3) assert_equal 'ar', @filters.slice('foobar', -2, 2) assert_equal 'ar', @filters.slice('foobar', -2, 1000) assert_equal 'r', @filters.slice('foobar', -1) assert_equal '', @filters.slice(nil, 0) assert_equal '', @filters.slice('foobar', 100, 10) assert_equal '', @filters.slice('foobar', -100, 10) end def test_slice_on_arrays input = 'foobar'.split(//) assert_equal %w{o o b}, @filters.slice(input, 1, 3) assert_equal %w{o o b a r}, @filters.slice(input, 1, 1000) assert_equal %w{}, @filters.slice(input, 1, 0) assert_equal %w{o}, @filters.slice(input, 1, 1) assert_equal %w{b a r}, @filters.slice(input, 3, 3) assert_equal %w{a r}, @filters.slice(input, -2, 2) assert_equal %w{a r}, @filters.slice(input, -2, 1000) assert_equal %w{r}, @filters.slice(input, -1) assert_equal %w{}, @filters.slice(input, 100, 10) assert_equal %w{}, @filters.slice(input, -100, 10) end def test_truncate assert_equal '1234...', @filters.truncate('1234567890', 7) assert_equal '1234567890', @filters.truncate('1234567890', 20) assert_equal '...', @filters.truncate('1234567890', 0) assert_equal '1234567890', @filters.truncate('1234567890') assert_equal "测试...", @filters.truncate("测试测试测试测试", 5) end def test_split assert_equal ['12','34'], @filters.split('12~34', '~') assert_equal ['A? ',' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~') assert_equal ['A?Z'], @filters.split('A?Z', '~') # Regexp works although Liquid does not support. assert_equal ['A','Z'], @filters.split('AxZ', /x/) assert_equal [], @filters.split(nil, ' ') end def test_escape assert_equal '<strong>', @filters.escape('') assert_equal '<strong>', @filters.h('') end def test_escape_once assert_equal '<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk') end def test_url_encode assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com') assert_equal nil, @filters.url_encode(nil) end def test_truncatewords assert_equal 'one two three', @filters.truncatewords('one two three', 4) assert_equal 'one two...', @filters.truncatewords('one two three', 2) assert_equal 'one two three', @filters.truncatewords('one two three') assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15) assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5) end def test_strip_html assert_equal 'test', @filters.strip_html("
test
") assert_equal 'test', @filters.strip_html("
test
") assert_equal '', @filters.strip_html("") assert_equal '', @filters.strip_html("") assert_equal 'test', @filters.strip_html("test") assert_equal 'test', @filters.strip_html("test") assert_equal '', @filters.strip_html(nil) end def test_join assert_equal '1 2 3 4', @filters.join([1,2,3,4]) assert_equal '1 - 2 - 3 - 4', @filters.join([1,2,3,4], ' - ') end def test_sort assert_equal [1,2,3,4], @filters.sort([4,3,2,1]) assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a") end def test_legacy_sort_hash assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2}) end def test_numerical_vs_lexicographical_sort assert_equal [2, 10], @filters.sort([10, 2]) assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a") assert_equal ["10", "2"], @filters.sort(["10", "2"]) assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a") end def test_uniq assert_equal [1,3,2,4], @filters.uniq([1,1,3,2,3,1,4,3,2,1]) assert_equal [{"a" => 1}, {"a" => 3}, {"a" => 2}], @filters.uniq([{"a" => 1}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a") testdrop = TestDrop.new assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test') end def test_reverse assert_equal [4,3,2,1], @filters.reverse([1,2,3,4]) end def test_legacy_reverse_hash assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2) end def test_map assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a') assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}", 'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}] end def test_map_doesnt_call_arbitrary_stuff assert_template_result "", '{{ "foo" | map: "__id__" }}' assert_template_result "", '{{ "foo" | map: "inspect" }}' end def test_map_calls_to_liquid t = TestThing.new assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t] end def test_map_on_hashes assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}', "thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] } end def test_legacy_map_on_hashes_with_dynamic_key template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}" hash = { "foo" => { "bar" => 42 } } assert_template_result "42", template, "thing" => hash end def test_sort_calls_to_liquid t = TestThing.new Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t]) assert t.foo > 0 end def test_map_over_proc drop = TestDrop.new p = Proc.new{ drop } templ = '{{ procs | map: "test" }}' assert_template_result "testfoo", templ, "procs" => [p] end def test_map_works_on_enumerables assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new end def test_sort_works_on_enumerables assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new end def test_first_and_last_call_to_liquid assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new] assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new] end def test_date assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B") assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B") assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B") assert_equal 'May', @filters.date("2006-05-05 10:00:00", "%B") assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B") assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B") assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "") assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "") assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "") assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil) assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y") assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y") assert_equal "#{Date.today.year}", @filters.date('now', '%Y') assert_equal "#{Date.today.year}", @filters.date('today', '%Y') assert_equal nil, @filters.date(nil, "%B") with_timezone("UTC") do assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y") assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y") end end def test_first_last assert_equal 1, @filters.first([1,2,3]) assert_equal 3, @filters.last([1,2,3]) assert_equal nil, @filters.first([]) assert_equal nil, @filters.last([]) end def test_replace assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2) assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2) assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}" end def test_remove assert_equal ' ', @filters.remove("a a a a", 'a') assert_equal 'a a a', @filters.remove_first("a a a a", 'a ') assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}" end def test_pipes_in_string_arguments assert_template_result 'foobar', "{{ 'foo|bar' | remove: '|' }}" end def test_strip assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c " assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t" end def test_lstrip assert_template_result 'ab c ', "{{ source | lstrip }}", 'source' => " ab c " assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t" end def test_rstrip assert_template_result " ab c", "{{ source | rstrip }}", 'source' => " ab c " assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t" end def test_strip_newlines assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc" assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc" end def test_newlines_to_br assert_template_result "a
\nb
\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc" end def test_plus assert_template_result "2", "{{ 1 | plus:1 }}" assert_template_result "2.0", "{{ '1' | plus:'1.0' }}" end def test_minus assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1 assert_template_result "2.3", "{{ '4.3' | minus:'2' }}" end def test_times assert_template_result "12", "{{ 3 | times:4 }}" assert_template_result "0", "{{ 'foo' | times:4 }}" assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}" assert_template_result "7.25", "{{ 0.0725 | times:100 }}" end def test_divided_by assert_template_result "4", "{{ 12 | divided_by:3 }}" assert_template_result "4", "{{ 14 | divided_by:3 }}" assert_template_result "5", "{{ 15 | divided_by:3 }}" assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}" end def test_modulo assert_template_result "1", "{{ 3 | modulo:2 }}" end def test_round assert_template_result "5", "{{ input | round }}", 'input' => 4.6 assert_template_result "4", "{{ '4.3' | round }}" assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612 end def test_ceil assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6 assert_template_result "5", "{{ '4.3' | ceil }}" end def test_floor assert_template_result "4", "{{ input | floor }}", 'input' => 4.6 assert_template_result "4", "{{ '4.3' | floor }}" end def test_append assigns = {'a' => 'bc', 'b' => 'd' } assert_template_result('bcd',"{{ a | append: 'd'}}",assigns) assert_template_result('bcd',"{{ a | append: b}}",assigns) end def test_prepend assigns = {'a' => 'bc', 'b' => 'a' } assert_template_result('abc',"{{ a | prepend: 'a'}}",assigns) assert_template_result('abc',"{{ a | prepend: b}}",assigns) end def test_default assert_equal "foo", @filters.default("foo", "bar") assert_equal "bar", @filters.default(nil, "bar") assert_equal "bar", @filters.default("", "bar") assert_equal "bar", @filters.default(false, "bar") assert_equal "bar", @filters.default([], "bar") assert_equal "bar", @filters.default({}, "bar") end def test_cannot_access_private_methods assert_template_result('a',"{{ 'a' | to_number }}") end def test_date_raises_nothing assert_template_result('', "{{ '' | date: '%D' }}") assert_template_result('abc', "{{ 'abc' | date: '%D' }}") end private def with_timezone(tz) old_tz = ENV['TZ'] ENV['TZ'] = tz yield ensure ENV['TZ'] = old_tz end end # StandardFiltersTest liquid-3.0.6/test/integration/blank_test.rb0000644000004100000410000000624612563012415021023 0ustar www-datawww-datarequire 'test_helper' class FoobarTag < Liquid::Tag def render(*args) " " end Liquid::Template.register_tag('foobar', FoobarTag) end class BlankTestFileSystem def read_template_file(template_path, context) template_path end end class BlankTest < Minitest::Test include Liquid N = 10 def wrap_in_for(body) "{% for i in (1..#{N}) %}#{body}{% endfor %}" end def wrap_in_if(body) "{% if true %}#{body}{% endif %}" end def wrap(body) wrap_in_for(body) + wrap_in_if(body) end def test_new_tags_are_not_blank_by_default assert_template_result(" "*N, wrap_in_for("{% foobar %}")) end def test_loops_are_blank assert_template_result("", wrap_in_for(" ")) end def test_if_else_are_blank assert_template_result("", "{% if true %} {% elsif false %} {% else %} {% endif %}") end def test_unless_is_blank assert_template_result("", wrap("{% unless true %} {% endunless %}")) end def test_mark_as_blank_only_during_parsing assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}")) end def test_comments_are_blank assert_template_result("", wrap(" {% comment %} whatever {% endcomment %} ")) end def test_captures_are_blank assert_template_result("", wrap(" {% capture foo %} whatever {% endcapture %} ")) end def test_nested_blocks_are_blank_but_only_if_all_children_are assert_template_result("", wrap(wrap(" "))) assert_template_result("\n but this is not "*(N+1), wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %} {% if true %} but this is not {% endif %}})) end def test_assigns_are_blank assert_template_result("", wrap(' {% assign foo = "bar" %} ')) end def test_whitespace_is_blank assert_template_result("", wrap(" ")) assert_template_result("", wrap("\t")) end def test_whitespace_is_not_blank_if_other_stuff_is_present body = " x " assert_template_result(body*(N+1), wrap(body)) end def test_increment_is_not_blank assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}")) end def test_cycle_is_not_blank assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}")) end def test_raw_is_not_blank assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}")) end def test_include_is_blank Liquid::Template.file_system = BlankTestFileSystem.new assert_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}") assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}") assert_template_result " "*(N+1), wrap(" {% include ' ' %} ") end def test_case_is_blank assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} ")) assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} ")) assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} ")) end end liquid-3.0.6/test/integration/render_profiling_test.rb0000644000004100000410000001064512563012415023262 0ustar www-datawww-datarequire 'test_helper' class RenderProfilingTest < Minitest::Test include Liquid class ProfilingFileSystem def read_template_file(template_path, context) "Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}" end end def setup Liquid::Template.file_system = ProfilingFileSystem.new end def test_template_allows_flagging_profiling t = Template.parse("{{ 'a string' | upcase }}") t.render! assert_nil t.profiler end def test_parse_makes_available_simple_profiling t = Template.parse("{{ 'a string' | upcase }}", :profile => true) t.render! assert_equal 1, t.profiler.length node = t.profiler[0] assert_equal " 'a string' | upcase ", node.code end def test_render_ignores_raw_strings_when_profiling t = Template.parse("This is raw string\nstuff\nNewline", :profile => true) t.render! assert_equal 0, t.profiler.length end def test_profiling_includes_line_numbers_of_liquid_nodes t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true) t.render! assert_equal 2, t.profiler.length # {{ 'a string' | upcase }} assert_equal 1, t.profiler[0].line_number # {{ increment test }} assert_equal 2, t.profiler[1].line_number end def test_profiling_includes_line_numbers_of_included_partials t = Template.parse("{% include 'a_template' %}", :profile => true) t.render! included_children = t.profiler[0].children # {% assign template_name = 'a_template' %} assert_equal 1, included_children[0].line_number # {{ template_name }} assert_equal 2, included_children[1].line_number end def test_profiling_times_the_rendering_of_tokens t = Template.parse("{% include 'a_template' %}", :profile => true) t.render! node = t.profiler[0] refute_nil node.render_time end def test_profiling_times_the_entire_render t = Template.parse("{% include 'a_template' %}", :profile => true) t.render! assert t.profiler.total_render_time >= 0, "Total render time was not calculated" end def test_profiling_uses_include_to_mark_children t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true) t.render! include_node = t.profiler[1] assert_equal 2, include_node.children.length end def test_profiling_marks_children_with_the_name_of_included_partial t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true) t.render! include_node = t.profiler[1] include_node.children.each do |child| assert_equal "'a_template'", child.partial end end def test_profiling_supports_multiple_templates t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true) t.render! a_template = t.profiler[1] a_template.children.each do |child| assert_equal "'a_template'", child.partial end b_template = t.profiler[2] b_template.children.each do |child| assert_equal "'b_template'", child.partial end end def test_profiling_supports_rendering_the_same_partial_multiple_times t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true) t.render! a_template1 = t.profiler[1] a_template1.children.each do |child| assert_equal "'a_template'", child.partial end a_template2 = t.profiler[2] a_template2.children.each do |child| assert_equal "'a_template'", child.partial end end def test_can_iterate_over_each_profiling_entry t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true) t.render! timing_count = 0 t.profiler.each do |timing| timing_count += 1 end assert_equal 2, timing_count end def test_profiling_marks_children_of_if_blocks t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true) t.render! assert_equal 1, t.profiler.length assert_equal 2, t.profiler[0].children.length end def test_profiling_marks_children_of_for_blocks t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true) t.render!({"collection" => ["one", "two"]}) assert_equal 1, t.profiler.length # Will profile each invocation of the for block assert_equal 2, t.profiler[0].children.length end end liquid-3.0.6/test/integration/output_test.rb0000644000004100000410000000640212563012415021266 0ustar www-datawww-datarequire 'test_helper' module FunnyFilter def make_funny(input) 'LOL' end def cite_funny(input) "LOL: #{input}" end def add_smiley(input, smiley = ":-)") "#{input} #{smiley}" end def add_tag(input, tag = "p", id = "foo") %|<#{tag} id="#{id}">#{input}| end def paragraph(input) "

#{input}

" end def link_to(name, url) %|#{name}| end end class OutputTest < Minitest::Test include Liquid def setup @assigns = { 'best_cars' => 'bmw', 'car' => {'bmw' => 'good', 'gm' => 'bad'} } end def test_variable text = %| {{best_cars}} | expected = %| bmw | assert_equal expected, Template.parse(text).render!(@assigns) end def test_variable_traversing_with_two_brackets text = %({{ site.data.menu[include.menu][include.locale] }}) assert_equal "it works!", Template.parse(text).render!( "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } }, "include" => { "menu" => "foo", "locale" => "bar" } ) end def test_variable_traversing text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} | expected = %| good bad good | assert_equal expected, Template.parse(text).render!(@assigns) end def test_variable_piping text = %( {{ car.gm | make_funny }} ) expected = %| LOL | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_variable_piping_with_input text = %( {{ car.gm | cite_funny }} ) expected = %| LOL: bad | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_variable_piping_with_args text = %! {{ car.gm | add_smiley : ':-(' }} ! expected = %| bad :-( | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_variable_piping_with_no_args text = %! {{ car.gm | add_smiley }} ! expected = %| bad :-) | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_multiple_variable_piping_with_args text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} ! expected = %| bad :-( :-( | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_variable_piping_with_multiple_args text = %! {{ car.gm | add_tag : 'span', 'bar'}} ! expected = %| bad | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_variable_piping_with_variable_args text = %! {{ car.gm | add_tag : 'span', car.bmw}} ! expected = %| bad | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_multiple_pipings text = %( {{ best_cars | cite_funny | paragraph }} ) expected = %|

LOL: bmw

| assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end def test_link_to text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} ) expected = %| Typo | assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter]) end end # OutputTest liquid-3.0.6/test/integration/hash_ordering_test.rb0000644000004100000410000000067412563012415022547 0ustar www-datawww-datarequire 'test_helper' module MoneyFilter def money(input) sprintf(' %d$ ', input) end end module CanadianMoneyFilter def money(input) sprintf(' %d$ CAD ', input) end end class HashOrderingTest < Minitest::Test include Liquid def test_global_register_order with_global_filter(MoneyFilter, CanadianMoneyFilter) do assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil) end end end liquid-3.0.6/test/integration/template_test.rb0000644000004100000410000001505112563012415021541 0ustar www-datawww-datarequire 'test_helper' class TemplateContextDrop < Liquid::Drop def before_method(method) method end def foo 'fizzbuzz' end def baz @context.registers['lulz'] end end class SomethingWithLength def length nil end liquid_methods :length end class ErroneousDrop < Liquid::Drop def bad_method raise 'ruby error in drop' end end class TemplateTest < Minitest::Test include Liquid def test_instance_assigns_persist_on_same_template_object_between_parses t = Template.new assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render! assert_equal 'from instance assigns', t.parse("{{ foo }}").render! end def test_instance_assigns_persist_on_same_template_parsing_between_renders t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}") assert_equal 'foo', t.render! assert_equal 'foofoo', t.render! end def test_custom_assigns_do_not_persist_on_same_template t = Template.new assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns') assert_equal '', t.parse("{{ foo }}").render! end def test_custom_assigns_squash_instance_assigns t = Template.new assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render! assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns') end def test_persistent_assigns_squash_instance_assigns t = Template.new assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render! t.assigns['foo'] = 'from persistent assigns' assert_equal 'from persistent assigns', t.parse("{{ foo }}").render! end def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders t = Template.new t.assigns['number'] = lambda { @global ||= 0; @global += 1 } assert_equal '1', t.parse("{{number}}").render! assert_equal '1', t.parse("{{number}}").render! assert_equal '1', t.render! @global = nil end def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders t = Template.new assigns = {'number' => lambda { @global ||= 0; @global += 1 }} assert_equal '1', t.parse("{{number}}").render!(assigns) assert_equal '1', t.parse("{{number}}").render!(assigns) assert_equal '1', t.render!(assigns) @global = nil end def test_resource_limits_works_with_custom_length_method t = Template.parse("{% assign foo = bar %}") t.resource_limits = { :render_length_limit => 42 } assert_equal "", t.render!("bar" => SomethingWithLength.new) end def test_resource_limits_render_length t = Template.parse("0123456789") t.resource_limits = { :render_length_limit => 5 } assert_equal "Liquid error: Memory limits exceeded", t.render() assert t.resource_limits[:reached] t.resource_limits = { :render_length_limit => 10 } assert_equal "0123456789", t.render!() refute_nil t.resource_limits[:render_length_current] end def test_resource_limits_render_score t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}") t.resource_limits = { :render_score_limit => 50 } assert_equal "Liquid error: Memory limits exceeded", t.render() assert t.resource_limits[:reached] t = Template.parse("{% for a in (1..100) %} foo {% endfor %}") t.resource_limits = { :render_score_limit => 50 } assert_equal "Liquid error: Memory limits exceeded", t.render() assert t.resource_limits[:reached] t.resource_limits = { :render_score_limit => 200 } assert_equal (" foo " * 100), t.render!() refute_nil t.resource_limits[:render_score_current] end def test_resource_limits_assign_score t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}") t.resource_limits = { :assign_score_limit => 1 } assert_equal "Liquid error: Memory limits exceeded", t.render() assert t.resource_limits[:reached] t.resource_limits = { :assign_score_limit => 2 } assert_equal "", t.render!() refute_nil t.resource_limits[:assign_score_current] end def test_resource_limits_aborts_rendering_after_first_error t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}") t.resource_limits = { :render_score_limit => 50 } assert_equal "Liquid error: Memory limits exceeded", t.render() assert t.resource_limits[:reached] end def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}") t.render!() assert t.resource_limits[:assign_score_current] > 0 assert t.resource_limits[:render_score_current] > 0 assert t.resource_limits[:render_length_current] > 0 end def test_default_resource_limits_unaffected_by_render_with_context context = Context.new t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}") t.render!(context) assert context.resource_limits[:assign_score_current] > 0 assert context.resource_limits[:render_score_current] > 0 assert context.resource_limits[:render_length_current] > 0 refute Template.default_resource_limits.key?(:assign_score_current) refute Template.default_resource_limits.key?(:render_score_current) refute Template.default_resource_limits.key?(:render_length_current) end def test_can_use_drop_as_context t = Template.new t.registers['lulz'] = 'haha' drop = TemplateContextDrop.new assert_equal 'fizzbuzz', t.parse('{{foo}}').render!(drop) assert_equal 'bar', t.parse('{{bar}}').render!(drop) assert_equal 'haha', t.parse("{{baz}}").render!(drop) end def test_render_bang_force_rethrow_errors_on_passed_context context = Context.new({'drop' => ErroneousDrop.new}) t = Template.new.parse('{{ drop.bad_method }}') e = assert_raises RuntimeError do t.render!(context) end assert_equal 'ruby error in drop', e.message end def test_exception_handler_doesnt_reraise_if_it_returns_false exception = nil Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false }) assert exception.is_a?(ZeroDivisionError) end def test_exception_handler_does_reraise_if_it_returns_true exception = nil assert_raises(ZeroDivisionError) do Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true }) end assert exception.is_a?(ZeroDivisionError) end end liquid-3.0.6/test/integration/assign_test.rb0000644000004100000410000000265012563012415021213 0ustar www-datawww-datarequire 'test_helper' class AssignTest < Minitest::Test include Liquid def test_assign_with_hyphen_in_variable_name template_source = <<-END_TEMPLATE {% assign this-thing = 'Print this-thing' %} {{ this-thing }} END_TEMPLATE template = Template.parse(template_source) rendered = template.render! assert_equal "Print this-thing", rendered.strip end def test_assigned_variable assert_template_result('.foo.', '{% assign foo = values %}.{{ foo[0] }}.', 'values' => %w{foo bar baz}) assert_template_result('.bar.', '{% assign foo = values %}.{{ foo[1] }}.', 'values' => %w{foo bar baz}) end def test_assign_with_filter assert_template_result('.bar.', '{% assign foo = values | split: "," %}.{{ foo[1] }}.', 'values' => "foo,bar,baz") end def test_assign_syntax_error assert_match_syntax_error(/assign/, '{% assign foo not values %}.', 'values' => "foo,bar,baz") end def test_assign_uses_error_mode with_error_mode(:strict) do assert_raises(SyntaxError) do Template.parse("{% assign foo = ('X' | downcase) %}") end end with_error_mode(:lax) do assert Template.parse("{% assign foo = ('X' | downcase) %}") end end end # AssignTest liquid-3.0.6/test/integration/filter_test.rb0000644000004100000410000000757212563012415021224 0ustar www-datawww-datarequire 'test_helper' module MoneyFilter def money(input) sprintf(' %d$ ', input) end def money_with_underscore(input) sprintf(' %d$ ', input) end end module CanadianMoneyFilter def money(input) sprintf(' %d$ CAD ', input) end end module SubstituteFilter def substitute(input, params={}) input.gsub(/%\{(\w+)\}/) { |match| params[$1] } end end class FiltersTest < Minitest::Test include Liquid module OverrideObjectMethodFilter def tap(input) "tap overridden" end end def setup @context = Context.new end def test_local_filter @context['var'] = 1000 @context.add_filters(MoneyFilter) assert_equal ' 1000$ ', Variable.new("var | money").render(@context) end def test_underscore_in_filter_name @context['var'] = 1000 @context.add_filters(MoneyFilter) assert_equal ' 1000$ ', Variable.new("var | money_with_underscore").render(@context) end def test_second_filter_overwrites_first @context['var'] = 1000 @context.add_filters(MoneyFilter) @context.add_filters(CanadianMoneyFilter) assert_equal ' 1000$ CAD ', Variable.new("var | money").render(@context) end def test_size @context['var'] = 'abcd' @context.add_filters(MoneyFilter) assert_equal 4, Variable.new("var | size").render(@context) end def test_join @context['var'] = [1,2,3,4] assert_equal "1 2 3 4", Variable.new("var | join").render(@context) end def test_sort @context['value'] = 3 @context['numbers'] = [2,1,4,3] @context['words'] = ['expected', 'as', 'alphabetic'] @context['arrays'] = ['flower', 'are'] assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context) assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context) assert_equal [3], Variable.new("value | sort").render(@context) assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context) end def test_strip_html @context['var'] = "bla blub" assert_equal "bla blub", Variable.new("var | strip_html").render(@context) end def test_strip_html_ignore_comments_with_html @context['var'] = "bla blub" assert_equal "bla blub", Variable.new("var | strip_html").render(@context) end def test_capitalize @context['var'] = "blub" assert_equal "Blub", Variable.new("var | capitalize").render(@context) end def test_nonexistent_filter_is_ignored @context['var'] = 1000 assert_equal 1000, Variable.new("var | xyzzy").render(@context) end def test_filter_with_keyword_arguments @context['surname'] = 'john' @context.add_filters(SubstituteFilter) output = Variable.new(%! 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' !).render(@context) assert_equal 'hello john, doe', output end def test_override_object_method_in_filter assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, :filters => [OverrideObjectMethodFilter]) # tap still treated as a non-existent filter assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }) end end class FiltersInTemplate < Minitest::Test include Liquid def test_local_global with_global_filter(MoneyFilter) do assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil) assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter) assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter]) end end def test_local_filter_with_deprecated_syntax assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter) assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter]) end end # FiltersTest liquid-3.0.6/test/unit/0000755000004100000410000000000012563012415014774 5ustar www-datawww-dataliquid-3.0.6/test/unit/tag_unit_test.rb0000644000004100000410000000061212563012415020171 0ustar www-datawww-datarequire 'test_helper' class TagUnitTest < Minitest::Test include Liquid def test_tag tag = Tag.parse('tag', [], [], {}) assert_equal 'liquid::tag', tag.name assert_equal '', tag.render(Context.new) end def test_return_raw_text_of_tag tag = Tag.parse("long_tag", "param1, param2, param3", [], {}) assert_equal("long_tag param1, param2, param3", tag.raw) end end liquid-3.0.6/test/unit/tags/0000755000004100000410000000000012563012415015732 5ustar www-datawww-dataliquid-3.0.6/test/unit/tags/if_tag_unit_test.rb0000644000004100000410000000036312563012415021610 0ustar www-datawww-datarequire 'test_helper' class IfTagUnitTest < Minitest::Test def test_if_nodelist template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}') assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist end end liquid-3.0.6/test/unit/tags/case_tag_unit_test.rb0000644000004100000410000000043712563012415022127 0ustar www-datawww-datarequire 'test_helper' class CaseTagUnitTest < Minitest::Test include Liquid def test_case_nodelist template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}') assert_equal ['WHEN', 'ELSE'], template.root.nodelist[0].nodelist end end liquid-3.0.6/test/unit/tags/for_tag_unit_test.rb0000644000004100000410000000066312563012415022003 0ustar www-datawww-datarequire 'test_helper' class ForTagUnitTest < Minitest::Test def test_for_nodelist template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}') assert_equal ['FOR'], template.root.nodelist[0].nodelist end def test_for_else_nodelist template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}') assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist end end liquid-3.0.6/test/unit/block_unit_test.rb0000644000004100000410000000342412563012415020514 0ustar www-datawww-datarequire 'test_helper' class BlockUnitTest < Minitest::Test include Liquid def test_blankspace template = Liquid::Template.parse(" ") assert_equal [" "], template.root.nodelist end def test_variable_beginning template = Liquid::Template.parse("{{funk}} ") assert_equal 2, template.root.nodelist.size assert_equal Variable, template.root.nodelist[0].class assert_equal String, template.root.nodelist[1].class end def test_variable_end template = Liquid::Template.parse(" {{funk}}") assert_equal 2, template.root.nodelist.size assert_equal String, template.root.nodelist[0].class assert_equal Variable, template.root.nodelist[1].class end def test_variable_middle template = Liquid::Template.parse(" {{funk}} ") assert_equal 3, template.root.nodelist.size assert_equal String, template.root.nodelist[0].class assert_equal Variable, template.root.nodelist[1].class assert_equal String, template.root.nodelist[2].class end def test_variable_many_embedded_fragments template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ") assert_equal 7, template.root.nodelist.size assert_equal [String, Variable, String, Variable, String, Variable, String], block_types(template.root.nodelist) end def test_with_block template = Liquid::Template.parse(" {% comment %} {% endcomment %} ") assert_equal [String, Comment, String], block_types(template.root.nodelist) assert_equal 3, template.root.nodelist.size end def test_with_custom_tag Liquid::Template.register_tag("testtag", Block) assert Liquid::Template.parse( "{% testtag %} {% endtesttag %}") end private def block_types(nodelist) nodelist.collect { |node| node.class } end end # VariableTest liquid-3.0.6/test/unit/condition_unit_test.rb0000644000004100000410000001113312563012415021404 0ustar www-datawww-datarequire 'test_helper' class ConditionUnitTest < Minitest::Test include Liquid def test_basic_condition assert_equal false, Condition.new('1', '==', '2').evaluate assert_equal true, Condition.new('1', '==', '1').evaluate end def test_default_operators_evalute_true assert_evalutes_true '1', '==', '1' assert_evalutes_true '1', '!=', '2' assert_evalutes_true '1', '<>', '2' assert_evalutes_true '1', '<', '2' assert_evalutes_true '2', '>', '1' assert_evalutes_true '1', '>=', '1' assert_evalutes_true '2', '>=', '1' assert_evalutes_true '1', '<=', '2' assert_evalutes_true '1', '<=', '1' # negative numbers assert_evalutes_true '1', '>', '-1' assert_evalutes_true '-1', '<', '1' assert_evalutes_true '1.0', '>', '-1.0' assert_evalutes_true '-1.0', '<', '1.0' end def test_default_operators_evalute_false assert_evalutes_false '1', '==', '2' assert_evalutes_false '1', '!=', '1' assert_evalutes_false '1', '<>', '1' assert_evalutes_false '1', '<', '0' assert_evalutes_false '2', '>', '4' assert_evalutes_false '1', '>=', '3' assert_evalutes_false '2', '>=', '4' assert_evalutes_false '1', '<=', '0' assert_evalutes_false '1', '<=', '0' end def test_contains_works_on_strings assert_evalutes_true "'bob'", 'contains', "'o'" assert_evalutes_true "'bob'", 'contains', "'b'" assert_evalutes_true "'bob'", 'contains', "'bo'" assert_evalutes_true "'bob'", 'contains', "'ob'" assert_evalutes_true "'bob'", 'contains', "'bob'" assert_evalutes_false "'bob'", 'contains', "'bob2'" assert_evalutes_false "'bob'", 'contains', "'a'" assert_evalutes_false "'bob'", 'contains', "'---'" end def test_invalid_comparation_operator assert_evaluates_argument_error "1", '~~', '0' end def test_comparation_of_int_and_str assert_evaluates_argument_error "'1'", '>', '0' assert_evaluates_argument_error "'1'", '<', '0' assert_evaluates_argument_error "'1'", '>=', '0' assert_evaluates_argument_error "'1'", '<=', '0' end def test_contains_works_on_arrays @context = Liquid::Context.new @context['array'] = [1,2,3,4,5] assert_evalutes_false "array", 'contains', '0' assert_evalutes_true "array", 'contains', '1' assert_evalutes_true "array", 'contains', '2' assert_evalutes_true "array", 'contains', '3' assert_evalutes_true "array", 'contains', '4' assert_evalutes_true "array", 'contains', '5' assert_evalutes_false "array", 'contains', '6' assert_evalutes_false "array", 'contains', '"1"' end def test_contains_returns_false_for_nil_operands @context = Liquid::Context.new assert_evalutes_false "not_assigned", 'contains', '0' assert_evalutes_false "0", 'contains', 'not_assigned' end def test_contains_return_false_on_wrong_data_type assert_evalutes_false "1", 'contains', '0' end def test_or_condition condition = Condition.new('1', '==', '2') assert_equal false, condition.evaluate condition.or Condition.new('2', '==', '1') assert_equal false, condition.evaluate condition.or Condition.new('1', '==', '1') assert_equal true, condition.evaluate end def test_and_condition condition = Condition.new('1', '==', '1') assert_equal true, condition.evaluate condition.and Condition.new('2', '==', '2') assert_equal true, condition.evaluate condition.and Condition.new('2', '==', '1') assert_equal false, condition.evaluate end def test_should_allow_custom_proc_operator Condition.operators['starts_with'] = Proc.new { |cond, left, right| left =~ %r{^#{right}} } assert_evalutes_true "'bob'", 'starts_with', "'b'" assert_evalutes_false "'bob'", 'starts_with', "'o'" ensure Condition.operators.delete 'starts_with' end def test_left_or_right_may_contain_operators @context = Liquid::Context.new @context['one'] = @context['another'] = "gnomeslab-and-or-liquid" assert_evalutes_true "one", '==', "another" end private def assert_evalutes_true(left, op, right) assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new), "Evaluated false: #{left} #{op} #{right}" end def assert_evalutes_false(left, op, right) assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new), "Evaluated true: #{left} #{op} #{right}" end def assert_evaluates_argument_error(left, op, right) assert_raises(Liquid::ArgumentError) do Condition.new(left, op, right).evaluate(@context || Liquid::Context.new) end end end # ConditionTest liquid-3.0.6/test/unit/parser_unit_test.rb0000644000004100000410000000430712563012415020717 0ustar www-datawww-datarequire 'test_helper' class ParserUnitTest < Minitest::Test include Liquid def test_consume p = Parser.new("wat: 7") assert_equal 'wat', p.consume(:id) assert_equal ':', p.consume(:colon) assert_equal '7', p.consume(:number) end def test_jump p = Parser.new("wat: 7") p.jump(2) assert_equal '7', p.consume(:number) end def test_consume? p = Parser.new("wat: 7") assert_equal 'wat', p.consume?(:id) assert_equal false, p.consume?(:dot) assert_equal ':', p.consume(:colon) assert_equal '7', p.consume?(:number) end def test_id? p = Parser.new("wat 6 Peter Hegemon") assert_equal 'wat', p.id?('wat') assert_equal false, p.id?('endgame') assert_equal '6', p.consume(:number) assert_equal 'Peter', p.id?('Peter') assert_equal false, p.id?('Achilles') end def test_look p = Parser.new("wat 6 Peter Hegemon") assert_equal true, p.look(:id) assert_equal 'wat', p.consume(:id) assert_equal false, p.look(:comparison) assert_equal true, p.look(:number) assert_equal true, p.look(:id, 1) assert_equal false, p.look(:number, 1) end def test_expressions p = Parser.new("hi.there hi[5].! hi.there.bob") assert_equal 'hi.there', p.expression assert_equal 'hi[5].!', p.expression assert_equal 'hi.there.bob', p.expression p = Parser.new("567 6.0 'lol' \"wut\"") assert_equal '567', p.expression assert_equal '6.0', p.expression assert_equal "'lol'", p.expression assert_equal '"wut"', p.expression end def test_ranges p = Parser.new("(5..7) (1.5..9.6) (young..old) (hi[5].wat..old)") assert_equal '(5..7)', p.expression assert_equal '(1.5..9.6)', p.expression assert_equal '(young..old)', p.expression assert_equal '(hi[5].wat..old)', p.expression end def test_arguments p = Parser.new("filter: hi.there[5], keyarg: 7") assert_equal 'filter', p.consume(:id) assert_equal ':', p.consume(:colon) assert_equal 'hi.there[5]', p.argument assert_equal ',', p.consume(:comma) assert_equal 'keyarg: 7', p.argument end def test_invalid_expression assert_raises(SyntaxError) do p = Parser.new("==") p.expression end end end liquid-3.0.6/test/unit/variable_unit_test.rb0000644000004100000410000001134212563012415021205 0ustar www-datawww-datarequire 'test_helper' class VariableUnitTest < Minitest::Test include Liquid def test_variable var = Variable.new('hello') assert_equal VariableLookup.new('hello'), var.name end def test_filters var = Variable.new('hello | textileze') assert_equal VariableLookup.new('hello'), var.name assert_equal [['textileze',[]]], var.filters var = Variable.new('hello | textileze | paragraph') assert_equal VariableLookup.new('hello'), var.name assert_equal [['textileze',[]], ['paragraph',[]]], var.filters var = Variable.new(%! hello | strftime: '%Y'!) assert_equal VariableLookup.new('hello'), var.name assert_equal [['strftime',['%Y']]], var.filters var = Variable.new(%! 'typo' | link_to: 'Typo', true !) assert_equal 'typo', var.name assert_equal [['link_to',['Typo', true]]], var.filters var = Variable.new(%! 'typo' | link_to: 'Typo', false !) assert_equal 'typo', var.name assert_equal [['link_to',['Typo', false]]], var.filters var = Variable.new(%! 'foo' | repeat: 3 !) assert_equal 'foo', var.name assert_equal [['repeat',[3]]], var.filters var = Variable.new(%! 'foo' | repeat: 3, 3 !) assert_equal 'foo', var.name assert_equal [['repeat',[3,3]]], var.filters var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !) assert_equal 'foo', var.name assert_equal [['repeat',[3,3,3]]], var.filters var = Variable.new(%! hello | strftime: '%Y, okay?'!) assert_equal VariableLookup.new('hello'), var.name assert_equal [['strftime',['%Y, okay?']]], var.filters var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!) assert_equal VariableLookup.new('hello'), var.name assert_equal [['things',['%Y, okay?','the other one']]], var.filters end def test_filter_with_date_parameter var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!) assert_equal '2006-06-06', var.name assert_equal [['date',['%m/%d/%Y']]], var.filters end def test_filters_without_whitespace var = Variable.new('hello | textileze | paragraph') assert_equal VariableLookup.new('hello'), var.name assert_equal [['textileze',[]], ['paragraph',[]]], var.filters var = Variable.new('hello|textileze|paragraph') assert_equal VariableLookup.new('hello'), var.name assert_equal [['textileze',[]], ['paragraph',[]]], var.filters var = Variable.new("hello|replace:'foo','bar'|textileze") assert_equal VariableLookup.new('hello'), var.name assert_equal [['replace', ['foo', 'bar']], ['textileze', []]], var.filters end def test_symbol var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax) assert_equal VariableLookup.new('http://disney.com/logo.gif'), var.name assert_equal [['image',['med']]], var.filters end def test_string_to_filter var = Variable.new("'http://disney.com/logo.gif' | image: 'med' ") assert_equal 'http://disney.com/logo.gif', var.name assert_equal [['image',['med']]], var.filters end def test_string_single_quoted var = Variable.new(%| "hello" |) assert_equal 'hello', var.name end def test_string_double_quoted var = Variable.new(%| 'hello' |) assert_equal 'hello', var.name end def test_integer var = Variable.new(%| 1000 |) assert_equal 1000, var.name end def test_float var = Variable.new(%| 1000.01 |) assert_equal 1000.01, var.name end def test_string_with_special_chars var = Variable.new(%| 'hello! $!@.;"ddasd" ' |) assert_equal 'hello! $!@.;"ddasd" ', var.name end def test_string_dot var = Variable.new(%| test.test |) assert_equal VariableLookup.new('test.test'), var.name end def test_filter_with_keyword_arguments var = Variable.new(%! hello | things: greeting: "world", farewell: 'goodbye'!) assert_equal VariableLookup.new('hello'), var.name assert_equal [['things', [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters end def test_lax_filter_argument_parsing var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !, :error_mode => :lax) assert_equal VariableLookup.new('number_of_comments'), var.name assert_equal [['pluralize',['comment','comments']]], var.filters end def test_strict_filter_argument_parsing with_error_mode(:strict) do assert_raises(SyntaxError) do Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !) end end end def test_output_raw_source_of_variable var = Variable.new(%! name_of_variable | upcase !) assert_equal " name_of_variable | upcase ", var.raw end def test_variable_lookup_interface lookup = VariableLookup.new('a.b.c') assert_equal 'a', lookup.name assert_equal ['b', 'c'], lookup.lookups end end liquid-3.0.6/test/unit/context_unit_test.rb0000644000004100000410000003034412563012415021107 0ustar www-datawww-datarequire 'test_helper' class HundredCentes def to_liquid 100 end end class CentsDrop < Liquid::Drop def amount HundredCentes.new end def non_zero? true end end class ContextSensitiveDrop < Liquid::Drop def test @context['test'] end end class Category < Liquid::Drop attr_accessor :name def initialize(name) @name = name end def to_liquid CategoryDrop.new(self) end end class CategoryDrop attr_accessor :category, :context def initialize(category) @category = category end end class CounterDrop < Liquid::Drop def count @count ||= 0 @count += 1 end end class ArrayLike def fetch(index) end def [](index) @counts ||= [] @counts[index] ||= 0 @counts[index] += 1 end def to_liquid self end end class ContextUnitTest < Minitest::Test include Liquid def setup @context = Liquid::Context.new end def teardown Spy.teardown end def test_variables @context['string'] = 'string' assert_equal 'string', @context['string'] @context['num'] = 5 assert_equal 5, @context['num'] @context['time'] = Time.parse('2006-06-06 12:00:00') assert_equal Time.parse('2006-06-06 12:00:00'), @context['time'] @context['date'] = Date.today assert_equal Date.today, @context['date'] now = DateTime.now @context['datetime'] = now assert_equal now, @context['datetime'] @context['bool'] = true assert_equal true, @context['bool'] @context['bool'] = false assert_equal false, @context['bool'] @context['nil'] = nil assert_equal nil, @context['nil'] assert_equal nil, @context['nil'] end def test_variables_not_existing assert_equal nil, @context['does_not_exist'] end def test_scoping @context.push @context.pop assert_raises(Liquid::ContextError) do @context.pop end assert_raises(Liquid::ContextError) do @context.push @context.pop @context.pop end end def test_length_query @context['numbers'] = [1,2,3,4] assert_equal 4, @context['numbers.size'] @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4} assert_equal 4, @context['numbers.size'] @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000} assert_equal 1000, @context['numbers.size'] end def test_hyphenated_variable @context['oh-my'] = 'godz' assert_equal 'godz', @context['oh-my'] end def test_add_filter filter = Module.new do def hi(output) output + ' hi!' end end context = Context.new context.add_filters(filter) assert_equal 'hi? hi!', context.invoke(:hi, 'hi?') context = Context.new assert_equal 'hi?', context.invoke(:hi, 'hi?') context.add_filters(filter) assert_equal 'hi? hi!', context.invoke(:hi, 'hi?') end def test_only_intended_filters_make_it_there filter = Module.new do def hi(output) output + ' hi!' end end context = Context.new assert_equal "Wookie", context.invoke("hi", "Wookie") context.add_filters(filter) assert_equal "Wookie hi!", context.invoke("hi", "Wookie") end def test_add_item_in_outer_scope @context['test'] = 'test' @context.push assert_equal 'test', @context['test'] @context.pop assert_equal 'test', @context['test'] end def test_add_item_in_inner_scope @context.push @context['test'] = 'test' assert_equal 'test', @context['test'] @context.pop assert_equal nil, @context['test'] end def test_hierachical_data @context['hash'] = {"name" => 'tobi'} assert_equal 'tobi', @context['hash.name'] assert_equal 'tobi', @context['hash["name"]'] end def test_keywords assert_equal true, @context['true'] assert_equal false, @context['false'] end def test_digits assert_equal 100, @context['100'] assert_equal 100.00, @context['100.00'] end def test_strings assert_equal "hello!", @context['"hello!"'] assert_equal "hello!", @context["'hello!'"] end def test_merge @context.merge({ "test" => "test" }) assert_equal 'test', @context['test'] @context.merge({ "test" => "newvalue", "foo" => "bar" }) assert_equal 'newvalue', @context['test'] assert_equal 'bar', @context['foo'] end def test_array_notation @context['test'] = [1,2,3,4,5] assert_equal 1, @context['test[0]'] assert_equal 2, @context['test[1]'] assert_equal 3, @context['test[2]'] assert_equal 4, @context['test[3]'] assert_equal 5, @context['test[4]'] end def test_recoursive_array_notation @context['test'] = {'test' => [1,2,3,4,5]} assert_equal 1, @context['test.test[0]'] @context['test'] = [{'test' => 'worked'}] assert_equal 'worked', @context['test[0].test'] end def test_hash_to_array_transition @context['colors'] = { 'Blue' => ['003366','336699', '6699CC', '99CCFF'], 'Green' => ['003300','336633', '669966', '99CC99'], 'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'], 'Red' => ['660000','993333', 'CC6666', 'FF9999'] } assert_equal '003366', @context['colors.Blue[0]'] assert_equal 'FF9999', @context['colors.Red[3]'] end def test_try_first @context['test'] = [1,2,3,4,5] assert_equal 1, @context['test.first'] assert_equal 5, @context['test.last'] @context['test'] = {'test' => [1,2,3,4,5]} assert_equal 1, @context['test.test.first'] assert_equal 5, @context['test.test.last'] @context['test'] = [1] assert_equal 1, @context['test.first'] assert_equal 1, @context['test.last'] end def test_access_hashes_with_hash_notation @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]} assert_equal 5, @context['products["count"]'] assert_equal 'deepsnow', @context['products["tags"][0]'] assert_equal 'deepsnow', @context['products["tags"].first'] assert_equal 'draft151cm', @context['product["variants"][0]["title"]'] assert_equal 'element151cm', @context['product["variants"][1]["title"]'] assert_equal 'draft151cm', @context['product["variants"][0]["title"]'] assert_equal 'element151cm', @context['product["variants"].last["title"]'] end def test_access_variable_with_hash_notation @context['foo'] = 'baz' @context['bar'] = 'foo' assert_equal 'baz', @context['["foo"]'] assert_equal 'baz', @context['[bar]'] end def test_access_hashes_with_hash_access_variables @context['var'] = 'tags' @context['nested'] = {'var' => 'tags'} @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } assert_equal 'deepsnow', @context['products[var].first'] assert_equal 'freestyle', @context['products[nested.var].last'] end def test_hash_notation_only_for_hash_access @context['array'] = [1,2,3,4,5] @context['hash'] = {'first' => 'Hello'} assert_equal 1, @context['array.first'] assert_equal nil, @context['array["first"]'] assert_equal 'Hello', @context['hash["first"]'] end def test_first_can_appear_in_middle_of_callchain @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]} assert_equal 'draft151cm', @context['product.variants[0].title'] assert_equal 'element151cm', @context['product.variants[1].title'] assert_equal 'draft151cm', @context['product.variants.first.title'] assert_equal 'element151cm', @context['product.variants.last.title'] end def test_cents @context.merge( "cents" => HundredCentes.new ) assert_equal 100, @context['cents'] end def test_nested_cents @context.merge( "cents" => { 'amount' => HundredCentes.new} ) assert_equal 100, @context['cents.amount'] @context.merge( "cents" => { 'cents' => { 'amount' => HundredCentes.new} } ) assert_equal 100, @context['cents.cents.amount'] end def test_cents_through_drop @context.merge( "cents" => CentsDrop.new ) assert_equal 100, @context['cents.amount'] end def test_nested_cents_through_drop @context.merge( "vars" => {"cents" => CentsDrop.new} ) assert_equal 100, @context['vars.cents.amount'] end def test_drop_methods_with_question_marks @context.merge( "cents" => CentsDrop.new ) assert @context['cents.non_zero?'] end def test_context_from_within_drop @context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new ) assert_equal '123', @context['vars.test'] end def test_nested_context_from_within_drop @context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } ) assert_equal '123', @context['vars.local.test'] end def test_ranges @context.merge( "test" => '5' ) assert_equal (1..5), @context['(1..5)'] assert_equal (1..5), @context['(1..test)'] assert_equal (5..5), @context['(test..test)'] end def test_cents_through_drop_nestedly @context.merge( "cents" => {"cents" => CentsDrop.new} ) assert_equal 100, @context['cents.cents.amount'] @context.merge( "cents" => { "cents" => {"cents" => CentsDrop.new}} ) assert_equal 100, @context['cents.cents.cents.amount'] end def test_drop_with_variable_called_only_once @context['counter'] = CounterDrop.new assert_equal 1, @context['counter.count'] assert_equal 2, @context['counter.count'] assert_equal 3, @context['counter.count'] end def test_drop_with_key_called_only_once @context['counter'] = CounterDrop.new assert_equal 1, @context['counter["count"]'] assert_equal 2, @context['counter["count"]'] assert_equal 3, @context['counter["count"]'] end def test_proc_as_variable @context['dynamic'] = Proc.new { 'Hello' } assert_equal 'Hello', @context['dynamic'] end def test_lambda_as_variable @context['dynamic'] = proc { 'Hello' } assert_equal 'Hello', @context['dynamic'] end def test_nested_lambda_as_variable @context['dynamic'] = { "lambda" => proc { 'Hello' } } assert_equal 'Hello', @context['dynamic.lambda'] end def test_array_containing_lambda_as_variable @context['dynamic'] = [1,2, proc { 'Hello' } ,4,5] assert_equal 'Hello', @context['dynamic[2]'] end def test_lambda_is_called_once @context['callcount'] = proc { @global ||= 0; @global += 1; @global.to_s } assert_equal '1', @context['callcount'] assert_equal '1', @context['callcount'] assert_equal '1', @context['callcount'] @global = nil end def test_nested_lambda_is_called_once @context['callcount'] = { "lambda" => proc { @global ||= 0; @global += 1; @global.to_s } } assert_equal '1', @context['callcount.lambda'] assert_equal '1', @context['callcount.lambda'] assert_equal '1', @context['callcount.lambda'] @global = nil end def test_lambda_in_array_is_called_once @context['callcount'] = [1,2, proc { @global ||= 0; @global += 1; @global.to_s } ,4,5] assert_equal '1', @context['callcount[2]'] assert_equal '1', @context['callcount[2]'] assert_equal '1', @context['callcount[2]'] @global = nil end def test_access_to_context_from_proc @context.registers[:magic] = 345392 @context['magic'] = proc { @context.registers[:magic] } assert_equal 345392, @context['magic'] end def test_to_liquid_and_context_at_first_level @context['category'] = Category.new("foobar") assert_kind_of CategoryDrop, @context['category'] assert_equal @context, @context['category'].context end def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations mock_any = Spy.on_instance_method(Array, :any?) mock_empty = Spy.on_instance_method(Array, :empty?) mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through @context.has_interrupt? refute mock_any.has_been_called? assert mock_empty.has_been_called? end def test_variable_lookup_caches_markup mock_scan = Spy.on_instance_method(String, :scan).and_return(["string"]) @context['string'] = 'string' @context['string'] @context['string'] assert_equal 1, mock_scan.calls.size end def test_context_initialization_with_a_proc_in_environment contx = Context.new([:test => lambda { |c| c['poutine']}], {:test => :foo}) assert contx assert_nil contx['poutine'] end end # ContextTest liquid-3.0.6/test/unit/i18n_unit_test.rb0000644000004100000410000000164612563012415020205 0ustar www-datawww-datarequire 'test_helper' class I18nUnitTest < Minitest::Test include Liquid def setup @i18n = I18n.new(fixture("en_locale.yml")) end def test_simple_translate_string assert_equal "less is more", @i18n.translate("simple") end def test_nested_translate_string assert_equal "something wasn't right", @i18n.translate("errors.syntax.oops") end def test_single_string_interpolation assert_equal "something different", @i18n.translate("whatever", :something => "different") end # def test_raises_translation_error_on_undefined_interpolation_key # assert_raises I18n::TranslationError do # @i18n.translate("whatever", :oopstypos => "yes") # end # end def test_raises_unknown_translation assert_raises I18n::TranslationError do @i18n.translate("doesnt_exist") end end def test_sets_default_path_to_en assert_equal I18n::DEFAULT_LOCALE, I18n.new.path end end liquid-3.0.6/test/unit/lexer_unit_test.rb0000644000004100000410000000270712563012415020544 0ustar www-datawww-datarequire 'test_helper' class LexerUnitTest < Minitest::Test include Liquid def test_strings tokens = Lexer.new(%! 'this is a test""' "wat 'lol'"!).tokenize assert_equal [[:string,%!'this is a test""'!], [:string, %!"wat 'lol'"!], [:end_of_string]], tokens end def test_integer tokens = Lexer.new('hi 50').tokenize assert_equal [[:id,'hi'], [:number, '50'], [:end_of_string]], tokens end def test_float tokens = Lexer.new('hi 5.0').tokenize assert_equal [[:id,'hi'], [:number, '5.0'], [:end_of_string]], tokens end def test_comparison tokens = Lexer.new('== <> contains').tokenize assert_equal [[:comparison,'=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens end def test_specials tokens = Lexer.new('| .:').tokenize assert_equal [[:pipe, '|'], [:dot, '.'], [:colon, ':'], [:end_of_string]], tokens tokens = Lexer.new('[,]').tokenize assert_equal [[:open_square, '['], [:comma, ','], [:close_square, ']'], [:end_of_string]], tokens end def test_fancy_identifiers tokens = Lexer.new('hi! five?').tokenize assert_equal [[:id,'hi!'], [:id, 'five?'], [:end_of_string]], tokens end def test_whitespace tokens = Lexer.new("five|\n\t ==").tokenize assert_equal [[:id,'five'], [:pipe, '|'], [:comparison, '=='], [:end_of_string]], tokens end def test_unexpected_character assert_raises(SyntaxError) do Lexer.new("%").tokenize end end end liquid-3.0.6/test/unit/module_ex_unit_test.rb0000644000004100000410000000421312563012415021400 0ustar www-datawww-datarequire 'test_helper' class TestClassA liquid_methods :allowedA, :chainedB def allowedA 'allowedA' end def restrictedA 'restrictedA' end def chainedB TestClassB.new end end class TestClassB liquid_methods :allowedB, :chainedC def allowedB 'allowedB' end def chainedC TestClassC.new end end class TestClassC liquid_methods :allowedC def allowedC 'allowedC' end end class TestClassC::LiquidDropClass def another_allowedC 'another_allowedC' end end class ModuleExUnitTest < Minitest::Test include Liquid def setup @a = TestClassA.new @b = TestClassB.new @c = TestClassC.new end def test_should_create_LiquidDropClass assert TestClassA::LiquidDropClass assert TestClassB::LiquidDropClass assert TestClassC::LiquidDropClass end def test_should_respond_to_liquid assert @a.respond_to?(:to_liquid) assert @b.respond_to?(:to_liquid) assert @c.respond_to?(:to_liquid) end def test_should_return_LiquidDropClass_object assert @a.to_liquid.is_a?(TestClassA::LiquidDropClass) assert @b.to_liquid.is_a?(TestClassB::LiquidDropClass) assert @c.to_liquid.is_a?(TestClassC::LiquidDropClass) end def test_should_respond_to_liquid_methods assert @a.to_liquid.respond_to?(:allowedA) assert @a.to_liquid.respond_to?(:chainedB) assert @b.to_liquid.respond_to?(:allowedB) assert @b.to_liquid.respond_to?(:chainedC) assert @c.to_liquid.respond_to?(:allowedC) assert @c.to_liquid.respond_to?(:another_allowedC) end def test_should_not_respond_to_restricted_methods assert ! @a.to_liquid.respond_to?(:restricted) end def test_should_use_regular_objects_as_drops assert_template_result 'allowedA', "{{ a.allowedA }}", 'a'=>@a assert_template_result 'allowedB', "{{ a.chainedB.allowedB }}", 'a'=>@a assert_template_result 'allowedC', "{{ a.chainedB.chainedC.allowedC }}", 'a'=>@a assert_template_result 'another_allowedC', "{{ a.chainedB.chainedC.another_allowedC }}", 'a'=>@a assert_template_result '', "{{ a.restricted }}", 'a'=>@a assert_template_result '', "{{ a.unknown }}", 'a'=>@a end end # ModuleExTest liquid-3.0.6/test/unit/strainer_unit_test.rb0000644000004100000410000000411112563012415021243 0ustar www-datawww-datarequire 'test_helper' class StrainerUnitTest < Minitest::Test include Liquid module AccessScopeFilters def public_filter "public" end def private_filter "private" end private :private_filter end Strainer.global_filter(AccessScopeFilters) def test_strainer strainer = Strainer.create(nil) assert_equal 5, strainer.invoke('size', 'input') assert_equal "public", strainer.invoke("public_filter") end def test_stainer_raises_argument_error strainer = Strainer.create(nil) assert_raises(Liquid::ArgumentError) do strainer.invoke("public_filter", 1) end end def test_strainer_only_invokes_public_filter_methods strainer = Strainer.create(nil) assert_equal false, strainer.class.invokable?('__test__') assert_equal false, strainer.class.invokable?('test') assert_equal false, strainer.class.invokable?('instance_eval') assert_equal false, strainer.class.invokable?('__send__') assert_equal true, strainer.class.invokable?('size') # from the standard lib end def test_strainer_returns_nil_if_no_filter_method_found strainer = Strainer.create(nil) assert_nil strainer.invoke("private_filter") assert_nil strainer.invoke("undef_the_filter") end def test_strainer_returns_first_argument_if_no_method_and_arguments_given strainer = Strainer.create(nil) assert_equal "password", strainer.invoke("undef_the_method", "password") end def test_strainer_only_allows_methods_defined_in_filters strainer = Strainer.create(nil) assert_equal "1 + 1", strainer.invoke("instance_eval", "1 + 1") assert_equal "puts", strainer.invoke("__send__", "puts", "Hi Mom") assert_equal "has_method?", strainer.invoke("invoke", "has_method?", "invoke") end def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation a = Module.new b = Module.new strainer = Strainer.create(nil, [a,b]) assert_kind_of Strainer, strainer assert_kind_of a, strainer assert_kind_of b, strainer assert_kind_of Liquid::StandardFilters, strainer end end # StrainerTest liquid-3.0.6/test/unit/file_system_unit_test.rb0000644000004100000410000000211312563012415021737 0ustar www-datawww-datarequire 'test_helper' class FileSystemUnitTest < Minitest::Test include Liquid def test_default assert_raises(FileSystemError) do BlankFileSystem.new.read_template_file("dummy", {'dummy'=>'smarty'}) end end def test_local file_system = Liquid::LocalFileSystem.new("/some/path") assert_equal "/some/path/_mypartial.liquid" , file_system.full_path("mypartial") assert_equal "/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial") assert_raises(FileSystemError) do file_system.full_path("../dir/mypartial") end assert_raises(FileSystemError) do file_system.full_path("/dir/../../dir/mypartial") end assert_raises(FileSystemError) do file_system.full_path("/etc/passwd") end end def test_custom_template_filename_patterns file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html") assert_equal "/some/path/mypartial.html" , file_system.full_path("mypartial") assert_equal "/some/path/dir/mypartial.html", file_system.full_path("dir/mypartial") end end # FileSystemTest liquid-3.0.6/test/unit/regexp_unit_test.rb0000644000004100000410000000310312563012415020706 0ustar www-datawww-datarequire 'test_helper' class RegexpUnitTest < Minitest::Test include Liquid def test_empty assert_equal [], ''.scan(QuotedFragment) end def test_quote assert_equal ['"arg 1"'], '"arg 1"'.scan(QuotedFragment) end def test_words assert_equal ['arg1', 'arg2'], 'arg1 arg2'.scan(QuotedFragment) end def test_tags assert_equal ['', ''], ' '.scan(QuotedFragment) assert_equal [''], ''.scan(QuotedFragment) assert_equal ['', ''], %||.scan(QuotedFragment) end def test_double_quoted_words assert_equal ['arg1', 'arg2', '"arg 3"'], 'arg1 arg2 "arg 3"'.scan(QuotedFragment) end def test_single_quoted_words assert_equal ['arg1', 'arg2', "'arg 3'"], 'arg1 arg2 \'arg 3\''.scan(QuotedFragment) end def test_quoted_words_in_the_middle assert_equal ['arg1', 'arg2', '"arg 3"', 'arg4'], 'arg1 arg2 "arg 3" arg4 '.scan(QuotedFragment) end def test_variable_parser assert_equal ['var'], 'var'.scan(VariableParser) assert_equal ['var', 'method'], 'var.method'.scan(VariableParser) assert_equal ['var', '[method]'], 'var[method]'.scan(VariableParser) assert_equal ['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser) assert_equal ['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser) assert_equal ['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser) end end # RegexpTest liquid-3.0.6/test/unit/template_unit_test.rb0000644000004100000410000000362012563012415021233 0ustar www-datawww-datarequire 'test_helper' class TemplateUnitTest < Minitest::Test include Liquid def test_sets_default_localization_in_document t = Template.new t.parse('') assert_instance_of I18n, t.root.options[:locale] end def test_sets_default_localization_in_context_with_quick_initialization t = Template.new t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml"))) assert_instance_of I18n, t.root.options[:locale] assert_equal fixture("en_locale.yml"), t.root.options[:locale].path end def test_with_cache_classes_tags_returns_the_same_class original_cache_setting = Liquid.cache_classes Liquid.cache_classes = true original_klass = Class.new Object.send(:const_set, :CustomTag, original_klass) Template.register_tag('custom', CustomTag) Object.send(:remove_const, :CustomTag) new_klass = Class.new Object.send(:const_set, :CustomTag, new_klass) assert Template.tags['custom'].equal?(original_klass) ensure Object.send(:remove_const, :CustomTag) Template.tags.delete('custom') Liquid.cache_classes = original_cache_setting end def test_without_cache_classes_tags_reloads_the_class original_cache_setting = Liquid.cache_classes Liquid.cache_classes = false original_klass = Class.new Object.send(:const_set, :CustomTag, original_klass) Template.register_tag('custom', CustomTag) Object.send(:remove_const, :CustomTag) new_klass = Class.new Object.send(:const_set, :CustomTag, new_klass) assert Template.tags['custom'].equal?(new_klass) ensure Object.send(:remove_const, :CustomTag) Template.tags.delete('custom') Liquid.cache_classes = original_cache_setting end class FakeTag; end def test_tags_delete Template.register_tag('fake', FakeTag) assert_equal FakeTag, Template.tags['fake'] Template.tags.delete('fake') assert_nil Template.tags['fake'] end end liquid-3.0.6/test/unit/tokenizer_unit_test.rb0000644000004100000410000000272512563012415021437 0ustar www-datawww-datarequire 'test_helper' class TokenizerTest < Minitest::Test def test_tokenize_strings assert_equal [' '], tokenize(' ') assert_equal ['hello world'], tokenize('hello world') end def test_tokenize_variables assert_equal ['{{funk}}'], tokenize('{{funk}}') assert_equal [' ', '{{funk}}', ' '], tokenize(' {{funk}} ') assert_equal [' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], tokenize(' {{funk}} {{so}} {{brother}} ') assert_equal [' ', '{{ funk }}', ' '], tokenize(' {{ funk }} ') end def test_tokenize_blocks assert_equal ['{%comment%}'], tokenize('{%comment%}') assert_equal [' ', '{%comment%}', ' '], tokenize(' {%comment%} ') assert_equal [' ', '{%comment%}', ' ', '{%endcomment%}', ' '], tokenize(' {%comment%} {%endcomment%} ') assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ") end def test_calculate_line_numbers_per_token_with_profiling template = Liquid::Template.parse("", :profile => true) assert_equal [1], template.send(:tokenize, "{{funk}}").map(&:line_number) assert_equal [1, 1, 1], template.send(:tokenize, " {{funk}} ").map(&:line_number) assert_equal [1, 2, 2], template.send(:tokenize, "\n{{funk}}\n").map(&:line_number) assert_equal [1, 1, 3], template.send(:tokenize, " {{\n funk \n}} ").map(&:line_number) end private def tokenize(source) Liquid::Template.new.send(:tokenize, source) end end liquid-3.0.6/History.md0000644000004100000410000002212412563012415015022 0ustar www-datawww-data# Liquid Version History ## 3.0.5 / 2015-07-23 / branch "3-0-stable" * Fix test failure under certain timezones [Dylan Thacker-Smith] ## 3.0.4 / 2015-07-17 * Fix chained access to multi-dimensional hashes [Florian Weingarten] ## 3.0.3 / 2015-05-28 * Fix condition parse order in strict mode (#569) [Justin Li, pushrax] ## 3.0.2 / 2015-04-24 * Expose VariableLookup private members (#551) [Justin Li, pushrax] * Documentation fixes ## 3.0.1 / 2015-01-23 * Remove duplicate `index0` key in TableRow tag (#502) [Alfred Xing] ## 3.0.0 / 2014-11-12 * Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith] * Fixed condition with wrong data types, see #423 [Bogdan Gusiev] * Add url_encode to standard filters, see #421 [Derrick Reimer, djreimer] * Add uniq to standard filters [Florian Weingarten, fw42] * Add exception_handler feature, see #397 and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42] * Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl] * Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl] * Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal] * Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42] * Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith] * Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42] * Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith] * Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith] * Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan] * Add a to_s default for liquid drops, see #306 [Adam Doeler, releod] * Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42] * Make if, for & case tags return complete and consistent nodelists, see #250 [Nick Jones, dntj] * Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith] * Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk] * Fix resource counting bug with respond_to?(:length), see #263 [Florian Weingarten, fw42] * Allow specifying custom patterns for template filenames, see #284 [Andrei Gladkyi, agladkyi] * Allow drops to optimize loading a slice of elements, see #282 [Tom Burns, boourns] * Support for passing variables to snippets in subdirs, see #271 [Joost Hietbrink, joost] * Add a class cache to avoid runtime extend calls, see #249 [James Tucker, raggi] * Remove some legacy Ruby 1.8 compatibility code, see #276 [Florian Weingarten, fw42] * Add default filter to standard filters, see #267 [Derrick Reimer, djreimer] * Add optional strict parsing and warn parsing, see #235 [Tristan Hume, trishume] * Add I18n syntax error translation, see #241 [Simon Hørup Eskildsen, Sirupsen] * Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42] * Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42] * Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42] * Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42] ## 2.6.3 / 2015-07-23 / branch "2-6-stable" * Fix test failure under certain timezones [Dylan Thacker-Smith] ## 2.6.2 / 2015-01-23 * Remove duplicate hash key [Parker Moore] ## 2.6.1 / 2014-01-10 Security fix, cherry-picked from master (4e14a65): * Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk] * Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith] ## 2.6.0 / 2013-11-25 IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability. The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8. * Bugfix for #106: fix example servlet [gnowoel] * Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss] * Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice] * Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup, ndwebgroup] * Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten, fw42] * Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42] * Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet] * Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet] * Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528] * Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet] * Resource limits [Florian Weingarten, fw42] * Add reverse filter [Jay Strybis, unreal] * Add utf-8 support * Use array instead of Hash to keep the registered filters [Tasos Stathopoulos, astathopoulos] * Cache tokenized partial templates [Tom Burns, boourns] * Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer, stomar] * Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet] * Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn] ## 2.5.5 / 2014-01-10 / branch "2-5-stable" Security fix, cherry-picked from master (4e14a65): * Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk] * Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith] ## 2.5.4 / 2013-11-11 * Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528] ## 2.5.3 / 2013-10-09 * #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42] ## 2.5.2 / 2013-09-03 / deleted Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases. ## 2.5.1 / 2013-07-24 * #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42] ## 2.5.0 / 2013-03-06 * Prevent Object methods from being called on drops * Avoid symbol injection from liquid * Added break and continue statements * Fix filter parser for args without space separators * Add support for filter keyword arguments ## 2.4.0 / 2012-08-03 * Performance improvements * Allow filters in `assign` * Add `modulo` filter * Ruby 1.8, 1.9, and Rubinius compatibility fixes * Add support for `quoted['references']` in `tablerow` * Add support for Enumerable to `tablerow` * `strip_html` filter removes html comments ## 2.3.0 / 2011-10-16 * Several speed/memory improvements * Numerous bug fixes * Added support for MRI 1.9, Rubinius, and JRuby * Added support for integer drop parameters * Added epoch support to `date` filter * New `raw` tag that suppresses parsing * Added `else` option to `for` tag * New `increment` tag * New `split` filter ## 2.2.1 / 2010-08-23 * Added support for literal tags ## 2.2.0 / 2010-08-22 * Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0 * Merged some changed made by the community ## 1.9.0 / 2008-03-04 * Fixed gem install rake task * Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins ## Before 1.9.0 * Added If with or / and expressions * Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods. * Added more tags to standard library * Added include tag ( like partials in rails ) * [...] Gazillion of detail improvements * Added strainers as filter hosts for better security [Tobias Luetke] * Fixed that rails integration would call filter with the wrong "self" [Michael Geary] * Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke] * Removed count helper from standard lib. use size [Tobias Luetke] * Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond] * Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond] {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }} * Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke] class ProductDrop < Liquid::Drop def top_sales Shop.current.products.find(:all, :order => 'sales', :limit => 10 ) end end t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' ) t.render('product' => ProductDrop.new ) * Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond] liquid-3.0.6/README.md0000644000004100000410000000652212563012415014322 0ustar www-datawww-data[![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid) [![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid) # Liquid template engine * [Contributing guidelines](CONTRIBUTING.md) * [Version history](History.md) * [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics) * [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki) * [Website](http://liquidmarkup.org/) ## Introduction Liquid is a template engine which was written with very specific requirements: * It has to have beautiful and simple markup. Template engines which don't produce good looking markup are no fun to use. * It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote. * It has to be stateless. Compile and render steps have to be separate so that the expensive parsing and compiling can be done once and later on you can just render it passing in a hash with local variables and objects. ## Why you should use Liquid * You want to allow your users to edit the appearance of your application but don't want them to run **insecure code on your server**. * You want to render templates directly from the database. * You like smarty (PHP) style template engines. * You need a template engine which does HTML just as well as emails. * You don't like the markup of your current templating engine. ## What does it look like? ```html
    {% for product in products %}
  • {{ product.name }}

    Only {{ product.price | price }} {{ product.description | prettyprint | paragraph }}
  • {% endfor %}
``` ## How to use Liquid Liquid supports a very simple API based around the Liquid::Template class. For standard use you can just pass it the content of a file and call render with a parameters hash. ```ruby @template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template @template.render('name' => 'tobi') # => "hi tobi" ``` ### Error Modes Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted. Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make it very hard to debug and can lead to unexpected behaviour. Liquid also comes with a stricter parser that can be used when editing templates to give better error messages when templates are invalid. You can enable this new parser like this: ```ruby Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal Liquid::Template.error_mode = :lax # The default mode, accepts almost anything. ``` If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`: ```ruby Liquid::Template.parse(source, :error_mode => :strict) ``` This is useful for doing things like enabling strict mode only in the theme editor. It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created. It is also recommended that you use it in the template editors of existing apps to give editors better error messages.