qonfig 0.0.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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