module 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 => ->(cond, left, right) { cond.send(:equal_variables, left, right) }, '!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, '<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, '<'.freeze => :<, '>'.freeze => :>, '>='.freeze => :>=, '<='.freeze => :<=, 'contains'.freeze => lambda do |cond, left, right| if left && right && left.respond_to?(:include?) right = right.to_s if left.is_a?(String) left.include?(right) else false end end } 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?(Liquid::Expression::MethodLiteral) if right.respond_to?(left.method_name) return right.send(left.method_name) else return nil end end if right.is_a?(Liquid::Expression::MethodLiteral) if left.respond_to?(right.method_name) return left.send(right.method_name) 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.evaluate(left) if op.nil? left = context.evaluate(left) right = context.evaluate(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) && right.respond_to?(operation) begin left.send(operation, right) rescue ::ArgumentError => e raise Liquid::ArgumentError.new(e.message) end end end end class ElseCondition < Condition def else? true end def evaluate(_context) true end end end