File: configurable.rb

package info (click to toggle)
ruby-dry-configurable 0.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 184 kB
  • sloc: ruby: 397; makefile: 4
file content (191 lines) | stat: -rw-r--r-- 4,288 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
require 'dry/core/constants'
require 'dry/configurable/settings'
require 'dry/configurable/error'
require 'dry/configurable/version'

# A collection of micro-libraries, each intended to encapsulate
# a common task in Ruby
module Dry
  # A simple configuration mixin
  #
  # @example class-level configuration
  #
  #   class App
  #     extend Dry::Configurable
  #
  #     setting :database do
  #       setting :dsn, 'sqlite:memory'
  #     end
  #   end
  #
  #   App.config.database.dsn = 'jdbc:sqlite:memory'
  #   App.config.database.dsn
  #     # => "jdbc:sqlite:memory"
  #
  # @example instance-level configuration
  #
  #   class App
  #     include Dry::Configurable
  #
  #     setting :database
  #   end
  #
  #   production = App.new
  #   production.config.database = ENV['DATABASE_URL']
  #   production.finalize!
  #
  #   development = App.new
  #   development.config.database = 'jdbc:sqlite:memory'
  #   development.finalize!
  #
  # @api public
  module Configurable
    include Dry::Core::Constants

    module ClassMethods
      # @private
      def self.extended(base)
        base.instance_exec do
          @settings = Settings.new
        end
      end

      # Add a setting to the configuration
      #
      # @param [Mixed] key
      #   The accessor key for the configuration value
      # @param [Mixed] default
      #   The default config value
      #
      # @yield
      #   If a block is given, it will be evaluated in the context of
      #   a new configuration class, and bound as the default value
      #
      # @return [Dry::Configurable::Config]
      #
      # @api public
      def setting(key, value = Undefined, options = Undefined, &block)
        raise_already_defined_config(key) if _settings.config_defined?

        setting = _settings.add(key, value, options, &block)

        if setting.reader?
          readers = singleton_class < Configurable ? singleton_class : self
          readers.send(:define_method, setting.name) { config[setting.name] }
        end
      end

      # Return an array of setting names
      #
      # @return [Set]
      #
      # @api public
      def settings
        _settings.names
      end

      # @private no, really...
      def _settings
        @settings
      end

      private

      # @private
      def raise_already_defined_config(key)
        raise AlreadyDefinedConfig,
              "Cannot add setting +#{key}+, #{self} is already configured"
      end

      # @private
      def inherited(subclass)
        parent = self
        subclass.instance_exec do
          @settings = parent._settings.dup
        end

        if singleton_class < Configurable
          parent_config = @config
          subclass.instance_exec do
            @config = _settings.create_config
            @config.define!(parent_config.to_h) if parent_config.defined?
          end
        end

        super
      end
    end

    class << self
      # @private
      def extended(base)
        base.extend(ClassMethods)
        base.class_eval do
          @config = _settings.create_config
        end
      end

      # @private
      def included(base)
        base.extend(ClassMethods)
      end
    end

    # @private
    def initialize(*)
      @config = self.class._settings.create_config
      super
    end

    # Return configuration
    #
    # @return [Dry::Configurable::Config]
    #
    # @api public
    def config
      return @config if @config.defined?
      @config.define!
    end

    # Return configuration
    #
    # @yield [Dry::Configuration::Config]
    #
    # @return [Dry::Configurable::Config]
    #
    # @api public
    def configure
      raise FrozenConfig, 'Cannot modify frozen config' if frozen?
      yield(config) if block_given?
      self
    end

    # Finalize and freeze configuration
    #
    # @return [Dry::Configurable::Config]
    #
    # @api public
    def finalize!
      freeze
      config.finalize!
    end

    # @api public
    def dup
      super.tap do |copy|
        copy.instance_variable_set(:@config, config.dup)
      end
    end

    # @api public
    def clone
      if frozen?
        super
      else
        super.tap do |copy|
          copy.instance_variable_set(:@config, config.dup)
        end
      end
    end
  end
end