qonfig 0.0.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -2
  3. data/.jrubyrc +1 -0
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +15 -0
  6. data/.travis.yml +43 -4
  7. data/CHANGELOG.md +121 -0
  8. data/Gemfile +4 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +1060 -19
  11. data/Rakefile +18 -4
  12. data/bin/console +5 -11
  13. data/bin/rspec +55 -0
  14. data/bin/setup +1 -0
  15. data/gemfiles/with_external_deps.gemfile +8 -0
  16. data/gemfiles/without_external_deps.gemfile +5 -0
  17. data/lib/qonfig.rb +22 -2
  18. data/lib/qonfig/command_set.rb +67 -0
  19. data/lib/qonfig/commands.rb +15 -0
  20. data/lib/qonfig/commands/add_nested_option.rb +45 -0
  21. data/lib/qonfig/commands/add_option.rb +41 -0
  22. data/lib/qonfig/commands/base.rb +12 -0
  23. data/lib/qonfig/commands/compose.rb +37 -0
  24. data/lib/qonfig/commands/expose_yaml.rb +159 -0
  25. data/lib/qonfig/commands/load_from_env.rb +95 -0
  26. data/lib/qonfig/commands/load_from_env/value_converter.rb +84 -0
  27. data/lib/qonfig/commands/load_from_json.rb +56 -0
  28. data/lib/qonfig/commands/load_from_self.rb +73 -0
  29. data/lib/qonfig/commands/load_from_yaml.rb +58 -0
  30. data/lib/qonfig/configurable.rb +116 -0
  31. data/lib/qonfig/data_set.rb +213 -0
  32. data/lib/qonfig/data_set/class_builder.rb +27 -0
  33. data/lib/qonfig/data_set/validator.rb +7 -0
  34. data/lib/qonfig/dsl.rb +122 -0
  35. data/lib/qonfig/errors.rb +111 -0
  36. data/lib/qonfig/loaders.rb +9 -0
  37. data/lib/qonfig/loaders/basic.rb +38 -0
  38. data/lib/qonfig/loaders/json.rb +24 -0
  39. data/lib/qonfig/loaders/yaml.rb +24 -0
  40. data/lib/qonfig/plugins.rb +65 -0
  41. data/lib/qonfig/plugins/abstract.rb +13 -0
  42. data/lib/qonfig/plugins/access_mixin.rb +38 -0
  43. data/lib/qonfig/plugins/registry.rb +125 -0
  44. data/lib/qonfig/plugins/toml.rb +26 -0
  45. data/lib/qonfig/plugins/toml/commands/expose_toml.rb +146 -0
  46. data/lib/qonfig/plugins/toml/commands/load_from_toml.rb +49 -0
  47. data/lib/qonfig/plugins/toml/data_set.rb +19 -0
  48. data/lib/qonfig/plugins/toml/dsl.rb +27 -0
  49. data/lib/qonfig/plugins/toml/loaders/toml.rb +24 -0
  50. data/lib/qonfig/plugins/toml/tomlrb_fixes.rb +92 -0
  51. data/lib/qonfig/plugins/toml/uploaders/toml.rb +25 -0
  52. data/lib/qonfig/settings.rb +457 -0
  53. data/lib/qonfig/settings/builder.rb +18 -0
  54. data/lib/qonfig/settings/key_guard.rb +71 -0
  55. data/lib/qonfig/settings/lock.rb +60 -0
  56. data/lib/qonfig/uploaders.rb +10 -0
  57. data/lib/qonfig/uploaders/base.rb +18 -0
  58. data/lib/qonfig/uploaders/file.rb +55 -0
  59. data/lib/qonfig/uploaders/json.rb +35 -0
  60. data/lib/qonfig/uploaders/yaml.rb +93 -0
  61. data/lib/qonfig/version.rb +7 -1
  62. data/qonfig.gemspec +29 -17
  63. metadata +122 -16
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.12.0
5
+ class Qonfig::Commands::LoadFromTOML < Qonfig::Commands::Base
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.12.0
10
+ attr_reader :file_path
11
+
12
+ # @return [Boolean]
13
+ #
14
+ # @api private
15
+ # @since 0.12.0
16
+ attr_reader :strict
17
+
18
+ # @param file_path [String]
19
+ # @option strict [Boolean]
20
+ #
21
+ # @api private
22
+ # @since 0.12.0
23
+ def initialize(file_path, strict: true)
24
+ @file_path = file_path
25
+ @strict = strict
26
+ end
27
+
28
+ # @param settings [Qonfig::Settings]
29
+ # @return [void]
30
+ #
31
+ # @api private
32
+ # @since 0.12.0
33
+ def call(settings)
34
+ toml_data = Qonfig::Loaders::TOML.load_file(file_path, fail_on_unexist: strict)
35
+ toml_based_settings = build_data_set_class(toml_data).new.settings
36
+ settings.__append_settings__(toml_based_settings)
37
+ end
38
+
39
+ private
40
+
41
+ # @param toml_data [Hash]
42
+ # @return [Class<Qonfig::DataSet>]
43
+ #
44
+ # @api private
45
+ # @since 0.12.0
46
+ def build_data_set_class(toml_data)
47
+ Qonfig::DataSet::ClassBuilder.build_from_hash(toml_data)
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.12.0
5
+ class Qonfig::DataSet
6
+ # @option path [String]
7
+ # @option options [Hash<Symbol,Any>] Nothing, just for compatability and consistency
8
+ # @param value_processor [Block]
9
+ # @return [void]
10
+ #
11
+ # @api public
12
+ # @since 0.12.0
13
+ def save_to_toml(path:, options: Qonfig::Uploaders::TOML::DEFAULT_OPTIONS, &value_processor)
14
+ thread_safe_access do
15
+ Qonfig::Uploaders::TOML.upload(settings, path: path, options: options, &value_processor)
16
+ end
17
+ end
18
+ alias_method :dump_to_toml, :save_to_toml
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.12.0
5
+ module Qonfig::DSL
6
+ # @param file_path [String]
7
+ # @option strict [Boolean]
8
+ # @return [void]
9
+ #
10
+ # @api public
11
+ # @since 0.12.0
12
+ def load_from_toml(file_path, strict: true)
13
+ commands << Qonfig::Commands::LoadFromTOML.new(file_path, strict: strict)
14
+ end
15
+
16
+ # @param file_path [String]
17
+ # @option strict [Boolean]
18
+ # @option via [Symbol]
19
+ # @option env [Symbol, String]
20
+ # @return [void]
21
+ #
22
+ # @api public
23
+ # @since 0.12.0
24
+ def expose_toml(file_path, strict: true, via:, env:)
25
+ commands << Qonfig::Commands::ExposeTOML.new(file_path, strict: strict, via: via, env: env)
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.12.0
5
+ class Qonfig::Loaders::TOML < Qonfig::Loaders::Basic
6
+ class << self
7
+ # @param data [String]
8
+ # @return [Object]
9
+ #
10
+ # @api private
11
+ # @since 0.12.0
12
+ def load(data)
13
+ ::TomlRB.parse(ERB.new(data).result)
14
+ end
15
+
16
+ # @return [Object]
17
+ #
18
+ # @api private
19
+ # @since 0.12.0
20
+ def load_empty_data
21
+ ::TomlRB.parse('')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE:
4
+ # - dumper sorts settins keys as a collection of string or symbols only
5
+ # - settings values like { a: 1, 'b' => 2 } will fail on comparison errors (Symbol with String)
6
+ # - problem is located in TomlRB::Dumper#sort_pairs(hash) method
7
+ # - problem code: `hash.keys.sort.map` (failed on `.sort` part)
8
+ # - we can patch this code by explicit `.map(&:to_s)` before `.sort`
9
+ #
10
+ # @api private
11
+ # @since 0.12.0
12
+ module TomlRB::Dumper::SortFixPatch
13
+ private
14
+
15
+ # NOTE: target method for our fix
16
+ def sort_pairs(hash)
17
+ nested_pairs = []
18
+ simple_pairs = []
19
+ table_array_pairs = []
20
+
21
+ # NOTE: our fix (original code: `hash.keys.sort`) (for details see notes above)
22
+ fixed_keys_sort(hash).each do |key|
23
+ val = hash[key]
24
+ element = [key, val]
25
+
26
+ if val.is_a? Hash
27
+ nested_pairs << element
28
+ elsif val.is_a?(Array) && val.first.is_a?(Hash)
29
+ table_array_pairs << element
30
+ else
31
+ simple_pairs << element
32
+ end
33
+ end
34
+
35
+ [simple_pairs, nested_pairs, table_array_pairs]
36
+ end
37
+
38
+ # NOTE: our fix (for detales see notes above)
39
+ def fixed_keys_sort(hash)
40
+ hash.keys.sort_by(&:to_s)
41
+ end
42
+ end
43
+
44
+ # NOTE:
45
+ # - dumper uses ultra primitive way to conver objects to toml format
46
+ # - dumper represents nil values as a simple strings without quots,
47
+ # but should not represent them at all
48
+ # - dumper can not validate invalid structures
49
+ # (for example: [1, [2,3], nil] (invalid, cuz arrays should contain values of one type))
50
+ #
51
+ # @api private
52
+ # @since 0.12.0
53
+ module TomlRB::Dumper::ObjectConverterFix
54
+ private
55
+
56
+ # NOTE: target method for our fix
57
+ def dump_simple_pairs(simple_pairs)
58
+ simple_pairs.each do |key, val|
59
+ key = quote_key(key) unless bare_key? key
60
+ # NOTE: our fix (original code: `@toml_str << "#{key} = #{to_toml(val)}\n"`)
61
+ fixed_toml_value_append(key, val)
62
+ end
63
+ end
64
+
65
+ # NOTE: our fix
66
+ def fixed_toml_value_append(key, val)
67
+ @toml_str << "#{key} = #{fixed_to_toml(val)}\n" unless val.nil?
68
+ end
69
+
70
+ # NOTE our fix
71
+ def fixed_to_toml(object)
72
+ # NOTE: original code of #toml(obj):
73
+ # if object.is_a? Time
74
+ # object.strftime('%Y-%m-%dT%H:%M:%SZ')
75
+ # else
76
+ # object.inspect
77
+ # end
78
+
79
+ case object
80
+ when Time, DateTime, Date
81
+ object.strftime('%Y-%m-%dT%H:%M:%SZ')
82
+ else
83
+ # NOTE: validate result value via value parsing before dump
84
+ object.inspect.tap { |value| ::TomlRB.parse("sample = #{value}") }
85
+ end
86
+ end
87
+ end
88
+
89
+ # @since 0.12.0
90
+ TomlRB::Dumper.prepend(TomlRB::Dumper::SortFixPatch)
91
+ # @since 0.12.0
92
+ TomlRB::Dumper.prepend(TomlRB::Dumper::ObjectConverterFix)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.12.0
5
+ class Qonfig::Uploaders::TOML < Qonfig::Uploaders::File
6
+ class << self
7
+ # @param settings [Qonfig::Settings]
8
+ # @param options [Hash<Symbol,Any>]
9
+ # @param value_processor [Block]
10
+ # @return [String]
11
+ #
12
+ # @api private
13
+ # @since 0.12.0
14
+ def represent_settings(settings, options, &value_processor)
15
+ settings_hash =
16
+ if block_given?
17
+ settings.__to_hash__(transform_value: value_processor)
18
+ else
19
+ settings.__to_hash__
20
+ end
21
+
22
+ ::TomlRB.dump(settings_hash)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,457 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ # rubocop:disable Metrics/ClassLength
6
+ class Qonfig::Settings
7
+ require_relative 'settings/lock'
8
+ require_relative 'settings/builder'
9
+ require_relative 'settings/key_guard'
10
+
11
+ # @return [Proc]
12
+ #
13
+ # @api private
14
+ # @since 0.11.0
15
+ BASIC_SETTING_KEY_TRANSFORMER = (proc { |value| value }).freeze
16
+
17
+ # @return [Proc]
18
+ #
19
+ # @api private
20
+ # @since 0.11.0
21
+ BASIC_SETTING_VALUE_TRANSFORMER = (proc { |value| value }).freeze
22
+
23
+ # @return [Hash]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ attr_reader :__options__
28
+
29
+ # @api private
30
+ # @since 0.1.0
31
+ def initialize
32
+ @__options__ = {}
33
+ @__lock__ = Lock.new
34
+ end
35
+
36
+ # @param key [Symbol, String]
37
+ # @param value [Object]
38
+ # @return [void]
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ def __define_setting__(key, value)
43
+ __lock__.thread_safe_definition do
44
+ key = __indifferently_accessable_option_key__(key)
45
+
46
+ __prevent_core_method_intersection__(key)
47
+
48
+ case
49
+ when !__options__.key?(key)
50
+ __options__[key] = value
51
+ when __options__[key].is_a?(Qonfig::Settings) && value.is_a?(Qonfig::Settings)
52
+ __options__[key].__append_settings__(value)
53
+ else
54
+ __options__[key] = value
55
+ end
56
+
57
+ __define_accessor__(key)
58
+ end
59
+ end
60
+
61
+ # @param settings [Qonfig::Settings]
62
+ # @return [void]
63
+ #
64
+ # @api private
65
+ # @since 0.1.0
66
+ def __append_settings__(settings)
67
+ __lock__.thread_safe_merge do
68
+ settings.__options__.each_pair do |key, value|
69
+ __define_setting__(key, value)
70
+ end
71
+ end
72
+ end
73
+
74
+ # @param key [Symbol, String]
75
+ # @return [Object]
76
+ #
77
+ # @api public
78
+ # @since 0.1.0
79
+ def [](key)
80
+ __lock__.thread_safe_access { __get_value__(key) }
81
+ end
82
+
83
+ # @param key [String, Symbol]
84
+ # @param value [Object]
85
+ # @return [void]
86
+ #
87
+ # @api public
88
+ # @since 0.1.0
89
+ def []=(key, value)
90
+ __lock__.thread_safe_access { __set_value__(key, value) }
91
+ end
92
+
93
+ # @param options_map [Hash]
94
+ # @return [void]
95
+ #
96
+ # @api private
97
+ # @since 0.3.0
98
+ def __apply_values__(options_map)
99
+ __lock__.thread_safe_access { __set_values_from_map__(options_map) }
100
+ end
101
+
102
+ # @param keys [Array<String, Symbol>]
103
+ # @return [Object]
104
+ #
105
+ # @api private
106
+ # @since 0.2.0
107
+ def __dig__(*keys)
108
+ __lock__.thread_safe_access { __deep_access__(*keys) }
109
+ end
110
+
111
+ # @param keys [Array<String, Symbol>]
112
+ # @return [Hash]
113
+ #
114
+ # @api private
115
+ # @since 0.9.0
116
+ def __slice__(*keys)
117
+ __lock__.thread_safe_access { __deep_slice__(*keys) }
118
+ end
119
+
120
+ # @param keys [Array<String, Symbol>]
121
+ # @return [Hash, Any]
122
+ #
123
+ # @api private
124
+ # @since 0.10.0
125
+ def __slice_value__(*keys)
126
+ __lock__.thread_safe_access { __deep_slice_value__(*keys) }
127
+ end
128
+
129
+ # @option transform_key [Proc]
130
+ # @option transform_value [Proc]
131
+ # @return [Hash]
132
+ #
133
+ # @api private
134
+ # @since 0.1.0
135
+ # rubocop:disable Metrics/LineLength
136
+ def __to_hash__(transform_key: BASIC_SETTING_KEY_TRANSFORMER, transform_value: BASIC_SETTING_VALUE_TRANSFORMER)
137
+ unless transform_key.is_a?(Proc)
138
+ ::Kernel.raise(Qonfig::IncorrectKeyTransformerError, 'Key transformer should be a proc')
139
+ end
140
+
141
+ unless transform_value.is_a?(Proc)
142
+ ::Kernel.raise(Qonfig::IncorrectValueTransformerError, 'Value transformer should be a proc')
143
+ end
144
+
145
+ __lock__.thread_safe_access do
146
+ __build_hash_representation__(transform_key: transform_key, transform_value: transform_value)
147
+ end
148
+ end
149
+ # rubocop:enable Metrics/LineLength
150
+ alias_method :__to_h__, :__to_hash__
151
+
152
+ # @return [void]
153
+ #
154
+ # @api private
155
+ # @since 0.2.0
156
+ def __clear__
157
+ __lock__.thread_safe_access { __clear_option_values__ }
158
+ end
159
+
160
+ # @param method_name [String, Symbol]
161
+ # @param arguments [Array<Object>]
162
+ # @param block [Proc]
163
+ # @return [void]
164
+ #
165
+ # @raise [Qonfig::UnknownSettingError]
166
+ #
167
+ # @api private
168
+ # @since 0.1.0
169
+ def method_missing(method_name, *arguments, &block)
170
+ super
171
+ rescue NoMethodError
172
+ ::Kernel.raise(Qonfig::UnknownSettingError, "Setting with <#{method_name}> key doesnt exist!")
173
+ end
174
+
175
+ # @return [Boolean]
176
+ #
177
+ # @api private
178
+ # @since 0.1.0
179
+ def respond_to_missing?(method_name, include_private = false)
180
+ # :nocov:
181
+ __options__.key?(method_name.to_s) || __options__.key?(method_name.to_sym) || super
182
+ # :nocov:
183
+ end
184
+
185
+ # @return [void]
186
+ #
187
+ # @api private
188
+ # @since 0.1.0
189
+ def __freeze__
190
+ __lock__.thread_safe_access do
191
+ __options__.freeze
192
+
193
+ __options__.each_value do |value|
194
+ value.__freeze__ if value.is_a?(Qonfig::Settings)
195
+ end
196
+ end
197
+ end
198
+
199
+ # @return [Boolean]
200
+ #
201
+ # @api private
202
+ # @since 0.2.0
203
+ def __is_frozen__
204
+ __lock__.thread_safe_access { __options__.frozen? }
205
+ end
206
+
207
+ private
208
+
209
+ # @return [Qonfig::Settings::Lock]
210
+ #
211
+ # @api private
212
+ # @since 0.2.0
213
+ attr_reader :__lock__
214
+
215
+ # @param options_map [Hash]
216
+ # @return [void]
217
+ #
218
+ # @raise [Qonfig::ArgumentError]
219
+ # @raise [Qonfig::AmbiguousSettingValueError]
220
+ #
221
+ # @api private
222
+ # @since 0.3.0
223
+ def __set_values_from_map__(options_map)
224
+ ::Kernel.raise(
225
+ Qonfig::ArgumentError, 'Options map should be represented as a hash'
226
+ ) unless options_map.is_a?(Hash)
227
+
228
+ options_map.each_pair do |key, value|
229
+ current_value = __get_value__(key)
230
+
231
+ # NOTE: some duplications here was made only for the better code readability
232
+ case
233
+ when !current_value.is_a?(Qonfig::Settings)
234
+ __set_value__(key, value)
235
+ when current_value.is_a?(Qonfig::Settings) && value.is_a?(Hash)
236
+ current_value.__apply_values__(value)
237
+ when current_value.is_a?(Qonfig::Settings) && !value.is_a?(Hash)
238
+ ::Kernel.raise(
239
+ Qonfig::AmbiguousSettingValueError,
240
+ "Can not redefine option <#{key}> that contains nested options"
241
+ )
242
+ end
243
+ end
244
+ end
245
+
246
+ # @return [void]
247
+ #
248
+ # @raise [Qonfig::FrozenSettingsError]
249
+ #
250
+ # @api private
251
+ # @since 0.2.0
252
+ def __clear_option_values__
253
+ ::Kernel.raise(
254
+ Qonfig::FrozenSettingsError, 'Can not modify frozen settings'
255
+ ) if __options__.frozen?
256
+
257
+ __options__.each_pair do |key, value|
258
+ if value.is_a?(Qonfig::Settings)
259
+ value.__clear__
260
+ else
261
+ __options__[key] = nil
262
+ end
263
+ end
264
+ end
265
+
266
+ # @param key [String, Symbol]
267
+ # @return [Object]
268
+ #
269
+ # @raise [Qonfig::UnknownSettingError]
270
+ #
271
+ # @api private
272
+ # @since 0.2.0
273
+ def __get_value__(key)
274
+ key = __indifferently_accessable_option_key__(key)
275
+
276
+ unless __options__.key?(key)
277
+ ::Kernel.raise(Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!")
278
+ end
279
+
280
+ __options__[key]
281
+ end
282
+
283
+ # @param key [String, Symbol]
284
+ # @param value [Object]
285
+ # @return [void]
286
+ #
287
+ # @raise [Qonfig::UnknownSettingError]
288
+ # @raise [Qonfig::FrozenSettingsError]
289
+ # @raise [Qonfig::AmbiguousSettingValueError]
290
+ #
291
+ # @api private
292
+ # @since 0.2.0
293
+ def __set_value__(key, value)
294
+ key = __indifferently_accessable_option_key__(key)
295
+
296
+ unless __options__.key?(key)
297
+ ::Kernel.raise(Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!")
298
+ end
299
+
300
+ if __options__.frozen?
301
+ ::Kernel.raise(Qonfig::FrozenSettingsError, 'Can not modify frozen settings')
302
+ end
303
+
304
+ if __options__[key].is_a?(Qonfig::Settings)
305
+ ::Kernel.raise(
306
+ Qonfig::AmbiguousSettingValueError,
307
+ "Can not redefine option <#{key}> that contains nested options"
308
+ )
309
+ end
310
+
311
+ __options__[key] = value
312
+ end
313
+
314
+ # @param keys [Array<Symbol, String>]
315
+ # @return [Object]
316
+ #
317
+ # @raise [Qonfig::ArgumentError]
318
+ # @raise [Qonfig::UnknownSettingError]
319
+ #
320
+ # @api private
321
+ # @since 0.2.0
322
+ def __deep_access__(*keys)
323
+ ::Kernel.raise(Qonfig::ArgumentError, 'Key list can not be empty') if keys.empty?
324
+
325
+ result = __get_value__(keys.first)
326
+ rest_keys = Array(keys[1..-1])
327
+
328
+ case
329
+ when rest_keys.empty?
330
+ result
331
+ when !result.is_a?(Qonfig::Settings)
332
+ ::Kernel.raise(
333
+ Qonfig::UnknownSettingError,
334
+ 'Setting with required digging sequence does not exist!'
335
+ )
336
+ when result.is_a?(Qonfig::Settings)
337
+ result.__dig__(*rest_keys)
338
+ end
339
+ end
340
+
341
+ # @param keys [Array<Symbol, String>]
342
+ # @return [Hash]
343
+ #
344
+ # @raise [Qonfig::ArgumentError]
345
+ # @raise [Qonfig::UnknownSettingError]
346
+ #
347
+ # @api private
348
+ # @since 0.9.0
349
+ def __deep_slice__(*keys)
350
+ {}.tap do |result|
351
+ __deep_access__(*keys).tap do |setting|
352
+ required_key = __indifferently_accessable_option_key__(keys.last)
353
+ result[required_key] = setting.is_a?(Qonfig::Settings) ? setting.__to_h__ : setting
354
+ end
355
+ end
356
+ end
357
+
358
+ # @param keys [Array<Symbol, String>]
359
+ # @return [Hash]
360
+ #
361
+ # @raise [Qonfig::ArgumentError]
362
+ # @raise [Qonfig::UnknownSettingError]
363
+ #
364
+ # @api private
365
+ # @since 0.1.0
366
+ def __deep_slice_value__(*keys)
367
+ required_key = __indifferently_accessable_option_key__(keys.last)
368
+ __deep_slice__(*keys)[required_key]
369
+ end
370
+
371
+ # @param options_part [Hash]
372
+ # @option transform_key [Proc]
373
+ # @option transform_value [Proc]
374
+ # @return [Hash]
375
+ #
376
+ # @api private
377
+ # @since 0.2.0
378
+ def __build_hash_representation__(options_part = __options__, transform_key:, transform_value:)
379
+ options_part.each_with_object({}) do |(key, value), hash|
380
+ final_key = transform_key.call(key)
381
+
382
+ case
383
+ when value.is_a?(Hash)
384
+ hash[final_key] = __build_hash_representation__(
385
+ value,
386
+ transform_key: transform_key,
387
+ transform_value: transform_value
388
+ )
389
+ when value.is_a?(Qonfig::Settings)
390
+ hash[final_key] = value.__to_hash__(
391
+ transform_key: transform_key,
392
+ transform_value: transform_value
393
+ )
394
+ else
395
+ final_value = transform_value.call(value)
396
+ hash[final_key] = final_value
397
+ end
398
+ end
399
+ end
400
+
401
+ # @param key [Symbol, String]
402
+ # @return [void]
403
+ #
404
+ # @api private
405
+ # @since 0.1.0
406
+ def __define_accessor__(key)
407
+ define_singleton_method(key) do
408
+ self.[](key)
409
+ end
410
+
411
+ define_singleton_method("#{key}=") do |value|
412
+ self.[]=(key, value)
413
+ end
414
+
415
+ define_singleton_method("#{key}?") do
416
+ !!self.[](key)
417
+ end
418
+ end
419
+
420
+ # @param key [Symbol, String]
421
+ # @return [String]
422
+ #
423
+ # @raise [Qonfig::ArgumentError]
424
+ # @see Qonfig::Settings::KeyGuard
425
+ #
426
+ # @api private
427
+ # @since 0.2.0
428
+ def __indifferently_accessable_option_key__(key)
429
+ KeyGuard.new(key).prevent_incompatible_key_type!
430
+ key.to_s
431
+ end
432
+
433
+ # @param key [Symbol, String]
434
+ # @return [void]
435
+ #
436
+ # @raise [Qonfig::CoreMethodIntersectionError]
437
+ # @see Qonfig::Settings::KeyGuard
438
+ #
439
+ # @api private
440
+ # @since 0.2.0
441
+ def __prevent_core_method_intersection__(key)
442
+ KeyGuard.new(key).prevent_core_method_intersection!
443
+ end
444
+
445
+ # rubocop:disable Layout/ClassStructure
446
+ # @return [Array<String>]
447
+ #
448
+ # @api private
449
+ # @since 0.2.0
450
+ CORE_METHODS = Array(
451
+ instance_methods(false) |
452
+ private_instance_methods(false) |
453
+ %i[super define_singleton_method self]
454
+ ).map(&:to_s).freeze
455
+ # rubocop:enable Layout/ClassStructure
456
+ end
457
+ # rubocop:enable Metrics/ClassLength