File: proxy.rb

package info (click to toggle)
ruby-configurate 0.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 200 kB
  • sloc: ruby: 992; makefile: 5
file content (113 lines) | stat: -rw-r--r-- 2,969 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# frozen_string_literal: true

module Configurate
  # Proxy object to support nested settings
  #
  # *Cavehats*: Since this object is always true, adding a +?+ at the end
  # returns the value, if found, instead of the proxy object.
  # So instead of +if settings.foo.bar+ use +if settings.foo.bar?+
  # to check for boolean values, +if settings.foo.bar.nil?+ to
  # check for nil values and of course you can do +if settings.foo.bar.present?+ to check for
  # empty values if you're in Rails. Call {#get} to actually return the value,
  # commonly when doing +settings.foo.bar.get || "default"+. Also don't
  # use this in case statements since +Module#===+ can't be fooled, again
  # call {#get}.
  #
  # If a setting ends with +=+ it's too called directly, just like with +?+.
  class Proxy < BasicObject
    # @param lookup_chain [#lookup]
    def initialize lookup_chain
      @lookup_chain = lookup_chain
      @setting_path = SettingPath.new
    end

    def !
      !target
    end

    %i[!= == eql? coerce].each do |method|
      define_method method do |other|
        target.public_send method, target_or_object(other)
      end
    end

    {
      to_int:  :to_i,
      to_hash: :to_h,
      to_str:  :to_s,
      to_ary:  :to_a
    }.each do |method, converter|
      define_method method do
        value = target
        return value.public_send converter if value.respond_to? converter

        value.public_send method
      end
    end

    def _proxy?
      true
    end

    def respond_to? method, include_private=false
      method == :_proxy? || target_respond_to?(method, include_private)
    end

    def send *args, &block
      __send__(*args, &block)
    end
    alias_method :public_send, :send

    def singleton_class
      target.singleton_class
    rescue ::TypeError
      class << self
        self
      end
    end

    # rubocop:disable Style/MethodMissingSuper we handle all calls
    # rubocop:disable Style/MissingRespondToMissing we override respond_to? instead

    def method_missing setting, *args, &block
      return target.public_send(setting, *args, &block) if target_respond_to? setting

      @setting_path << setting

      return target(*args) if @setting_path.question_action_or_setter?

      self
    end
    # rubocop:enable all

    # Get the setting at the current path, if found.
    # (see LookupChain#lookup)
    def target *args
      return if @setting_path.empty?

      @lookup_chain.lookup @setting_path, *args
    end
    alias_method :get, :target

    private

    COMMON_KEY_NAMES = %i[key method].freeze

    def target_respond_to? setting, include_private=false
      return false if COMMON_KEY_NAMES.include? setting

      value = target
      return false if proxy? value

      value.respond_to? setting, include_private
    end

    def proxy? obj
      obj.respond_to?(:_proxy?) && obj._proxy?
    end

    def target_or_object obj
      proxy?(obj) ? obj.target : obj
    end
  end
end