summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/liquid-4.0.4/lib/liquid/strainer.rb
blob: 76d56d23127fe68f61d0c9709ff4ad15ece8c11f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
require '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

    class << self
      attr_reader :filter_methods
    end

    def self.add_filter(filter)
      raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
      unless self.include?(filter)
        invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
        if invokable_non_public_methods.any?
          raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
        else
          send(:include, filter)
          @filter_methods.merge(filter.public_instance_methods.map(&:to_s))
        end
      end
    end

    def self.global_filter(filter)
      @@strainer_class_cache.clear
      @@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)
      elsif @context && @context.strict_filters
        raise Liquid::UndefinedFilter, "undefined filter #{method}"
      else
        args.first
      end
    rescue ::ArgumentError => e
      raise Liquid::ArgumentError, e.message, e.backtrace
    end
  end
end