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.
- checksums.yaml +4 -4
- data/.gitignore +6 -2
- data/.jrubyrc +1 -0
- data/.rspec +1 -1
- data/.rubocop.yml +15 -0
- data/.travis.yml +43 -4
- data/CHANGELOG.md +121 -0
- data/Gemfile +4 -2
- data/LICENSE.txt +1 -1
- data/README.md +1060 -19
- data/Rakefile +18 -4
- data/bin/console +5 -11
- data/bin/rspec +55 -0
- data/bin/setup +1 -0
- data/gemfiles/with_external_deps.gemfile +8 -0
- data/gemfiles/without_external_deps.gemfile +5 -0
- data/lib/qonfig.rb +22 -2
- data/lib/qonfig/command_set.rb +67 -0
- data/lib/qonfig/commands.rb +15 -0
- data/lib/qonfig/commands/add_nested_option.rb +45 -0
- data/lib/qonfig/commands/add_option.rb +41 -0
- data/lib/qonfig/commands/base.rb +12 -0
- data/lib/qonfig/commands/compose.rb +37 -0
- data/lib/qonfig/commands/expose_yaml.rb +159 -0
- data/lib/qonfig/commands/load_from_env.rb +95 -0
- data/lib/qonfig/commands/load_from_env/value_converter.rb +84 -0
- data/lib/qonfig/commands/load_from_json.rb +56 -0
- data/lib/qonfig/commands/load_from_self.rb +73 -0
- data/lib/qonfig/commands/load_from_yaml.rb +58 -0
- data/lib/qonfig/configurable.rb +116 -0
- data/lib/qonfig/data_set.rb +213 -0
- data/lib/qonfig/data_set/class_builder.rb +27 -0
- data/lib/qonfig/data_set/validator.rb +7 -0
- data/lib/qonfig/dsl.rb +122 -0
- data/lib/qonfig/errors.rb +111 -0
- data/lib/qonfig/loaders.rb +9 -0
- data/lib/qonfig/loaders/basic.rb +38 -0
- data/lib/qonfig/loaders/json.rb +24 -0
- data/lib/qonfig/loaders/yaml.rb +24 -0
- data/lib/qonfig/plugins.rb +65 -0
- data/lib/qonfig/plugins/abstract.rb +13 -0
- data/lib/qonfig/plugins/access_mixin.rb +38 -0
- data/lib/qonfig/plugins/registry.rb +125 -0
- data/lib/qonfig/plugins/toml.rb +26 -0
- data/lib/qonfig/plugins/toml/commands/expose_toml.rb +146 -0
- data/lib/qonfig/plugins/toml/commands/load_from_toml.rb +49 -0
- data/lib/qonfig/plugins/toml/data_set.rb +19 -0
- data/lib/qonfig/plugins/toml/dsl.rb +27 -0
- data/lib/qonfig/plugins/toml/loaders/toml.rb +24 -0
- data/lib/qonfig/plugins/toml/tomlrb_fixes.rb +92 -0
- data/lib/qonfig/plugins/toml/uploaders/toml.rb +25 -0
- data/lib/qonfig/settings.rb +457 -0
- data/lib/qonfig/settings/builder.rb +18 -0
- data/lib/qonfig/settings/key_guard.rb +71 -0
- data/lib/qonfig/settings/lock.rb +60 -0
- data/lib/qonfig/uploaders.rb +10 -0
- data/lib/qonfig/uploaders/base.rb +18 -0
- data/lib/qonfig/uploaders/file.rb +55 -0
- data/lib/qonfig/uploaders/json.rb +35 -0
- data/lib/qonfig/uploaders/yaml.rb +93 -0
- data/lib/qonfig/version.rb +7 -1
- data/qonfig.gemspec +29 -17
- 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
|