qonfig 0.29.0 → 0.31.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/.github/workflows/build.yml +50 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile +0 -3
- data/Gemfile.lock +77 -98
- data/LICENSE.txt +1 -1
- data/README.md +6 -4
- data/gemfiles/with_external_deps.gemfile +2 -0
- data/gemfiles/with_external_deps.gemfile.lock +84 -99
- data/gemfiles/without_external_deps.gemfile.lock +78 -95
- data/lib/qonfig/commands/definition/add_nested_option.rb +5 -1
- data/lib/qonfig/commands/definition/load_from_env/value_converter.rb +6 -6
- data/lib/qonfig/commands/definition/re_define_option.rb +5 -1
- data/lib/qonfig/commands/instantiation/values_file.rb +4 -6
- data/lib/qonfig/imports/direct_key.rb +4 -4
- data/lib/qonfig/imports/mappings.rb +4 -4
- data/lib/qonfig/plugins/pretty_print/requirements.rb +1 -1
- data/lib/qonfig/plugins/toml/loaders/dynamic.rb +1 -1
- data/lib/qonfig/plugins/toml/loaders.rb +1 -1
- data/lib/qonfig/plugins.rb +2 -4
- data/lib/qonfig/settings/callbacks.rb +9 -0
- data/lib/qonfig/settings.rb +104 -18
- data/lib/qonfig/uploaders/yaml.rb +1 -1
- data/lib/qonfig/validation/building/instance_builder/attribute_consistency.rb +2 -2
- data/lib/qonfig/version.rb +1 -1
- data/lib/qonfig.rb +1 -0
- data/qonfig.gemspec +8 -8
- data/spec/features/config_definition_and_representation_spec.rb +7 -7
- data/spec/features/freeze_state_spec.rb +10 -10
- data/spec/features/nested_access_caching_spec.rb +134 -0
- data/spec/features/plugins/pretty_print_spec.rb +15 -15
- data/spec/features/plugins/toml/save_to_toml_spec.rb +1 -1
- data/spec/features/plugins/vault/expose_vault_spec.rb +1 -1
- data/spec/features/run_code_with_temporary_settings_spec.rb +70 -68
- data/spec/features/save_to_file/save_to_json_spec.rb +38 -18
- metadata +37 -49
- data/.github/workflows/test.yml +0 -62
- data/.jrubyrc +0 -1
- data/sig/.keep +0 -0
|
@@ -1,139 +1,122 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
qonfig (0.
|
|
4
|
+
qonfig (0.31.0)
|
|
5
|
+
base64 (>= 0.2)
|
|
5
6
|
|
|
6
7
|
GEM
|
|
7
8
|
remote: https://rubygems.org/
|
|
8
9
|
specs:
|
|
9
|
-
activesupport (7.
|
|
10
|
+
activesupport (6.1.7.10)
|
|
10
11
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
11
12
|
i18n (>= 1.6, < 2)
|
|
12
13
|
minitest (>= 5.1)
|
|
13
14
|
tzinfo (~> 2.0)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
rubocop
|
|
17
|
-
rubocop-
|
|
15
|
+
zeitwerk (~> 2.3)
|
|
16
|
+
armitage-rubocop (1.68.0.2)
|
|
17
|
+
rubocop (= 1.68.0)
|
|
18
|
+
rubocop-capybara (= 2.21.0)
|
|
19
|
+
rubocop-factory_bot (= 2.26.1)
|
|
20
|
+
rubocop-performance (= 1.23.0)
|
|
21
|
+
rubocop-rails (= 2.27.0)
|
|
18
22
|
rubocop-rake (= 0.6.0)
|
|
19
|
-
rubocop-rspec (= 2.
|
|
23
|
+
rubocop-rspec (= 3.2.0)
|
|
24
|
+
rubocop-rspec_rails (= 2.30.0)
|
|
20
25
|
ast (2.4.2)
|
|
21
|
-
|
|
22
|
-
bundler (>= 1.2.0, < 3)
|
|
23
|
-
thor (~> 1.0)
|
|
24
|
-
ci-helper (0.5.0)
|
|
25
|
-
colorize (~> 0.8)
|
|
26
|
-
dry-inflector (~> 0.2)
|
|
27
|
-
umbrellio-sequel-plugins (~> 0.4)
|
|
26
|
+
base64 (0.2.0)
|
|
28
27
|
coderay (1.1.3)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
dry-inflector (0.2.1)
|
|
34
|
-
ffi (1.15.5)
|
|
35
|
-
i18n (1.10.0)
|
|
28
|
+
concurrent-ruby (1.3.4)
|
|
29
|
+
diff-lcs (1.5.1)
|
|
30
|
+
docile (1.4.1)
|
|
31
|
+
i18n (1.14.6)
|
|
36
32
|
concurrent-ruby (~> 1.0)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
parallel (1.22.1)
|
|
44
|
-
parser (3.1.2.0)
|
|
33
|
+
json (2.9.0)
|
|
34
|
+
language_server-protocol (3.17.0.3)
|
|
35
|
+
method_source (1.1.0)
|
|
36
|
+
minitest (5.25.4)
|
|
37
|
+
parallel (1.26.3)
|
|
38
|
+
parser (3.3.6.0)
|
|
45
39
|
ast (~> 2.4.1)
|
|
46
|
-
|
|
40
|
+
racc
|
|
41
|
+
pry (0.15.0)
|
|
47
42
|
coderay (~> 1.1)
|
|
48
43
|
method_source (~> 1.0)
|
|
49
|
-
|
|
44
|
+
racc (1.8.1)
|
|
45
|
+
rack (3.1.8)
|
|
50
46
|
rainbow (3.1.1)
|
|
51
|
-
rake (13.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
rspec-expectations (~> 3.11.0)
|
|
61
|
-
rspec-mocks (~> 3.11.0)
|
|
62
|
-
rspec-core (3.11.0)
|
|
63
|
-
rspec-support (~> 3.11.0)
|
|
64
|
-
rspec-expectations (3.11.0)
|
|
47
|
+
rake (13.2.1)
|
|
48
|
+
regexp_parser (2.9.3)
|
|
49
|
+
rspec (3.13.0)
|
|
50
|
+
rspec-core (~> 3.13.0)
|
|
51
|
+
rspec-expectations (~> 3.13.0)
|
|
52
|
+
rspec-mocks (~> 3.13.0)
|
|
53
|
+
rspec-core (3.13.2)
|
|
54
|
+
rspec-support (~> 3.13.0)
|
|
55
|
+
rspec-expectations (3.13.3)
|
|
65
56
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
66
|
-
rspec-support (~> 3.
|
|
67
|
-
rspec-mocks (3.
|
|
57
|
+
rspec-support (~> 3.13.0)
|
|
58
|
+
rspec-mocks (3.13.2)
|
|
68
59
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
69
|
-
rspec-support (~> 3.
|
|
70
|
-
rspec-support (3.
|
|
71
|
-
rubocop (1.
|
|
60
|
+
rspec-support (~> 3.13.0)
|
|
61
|
+
rspec-support (3.13.2)
|
|
62
|
+
rubocop (1.68.0)
|
|
63
|
+
json (~> 2.3)
|
|
64
|
+
language_server-protocol (>= 3.17.0)
|
|
72
65
|
parallel (~> 1.10)
|
|
73
|
-
parser (>= 3.
|
|
66
|
+
parser (>= 3.3.0.2)
|
|
74
67
|
rainbow (>= 2.2.2, < 4.0)
|
|
75
|
-
regexp_parser (>=
|
|
76
|
-
|
|
77
|
-
rubocop-ast (>= 1.18.0, < 2.0)
|
|
68
|
+
regexp_parser (>= 2.4, < 3.0)
|
|
69
|
+
rubocop-ast (>= 1.32.2, < 2.0)
|
|
78
70
|
ruby-progressbar (~> 1.7)
|
|
79
|
-
unicode-display_width (>=
|
|
80
|
-
rubocop-ast (1.
|
|
81
|
-
parser (>= 3.
|
|
82
|
-
rubocop-
|
|
83
|
-
rubocop (
|
|
84
|
-
|
|
85
|
-
|
|
71
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
72
|
+
rubocop-ast (1.37.0)
|
|
73
|
+
parser (>= 3.3.1.0)
|
|
74
|
+
rubocop-capybara (2.21.0)
|
|
75
|
+
rubocop (~> 1.41)
|
|
76
|
+
rubocop-factory_bot (2.26.1)
|
|
77
|
+
rubocop (~> 1.61)
|
|
78
|
+
rubocop-performance (1.23.0)
|
|
79
|
+
rubocop (>= 1.48.1, < 2.0)
|
|
80
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
|
81
|
+
rubocop-rails (2.27.0)
|
|
86
82
|
activesupport (>= 4.2.0)
|
|
87
83
|
rack (>= 1.1)
|
|
88
|
-
rubocop (>= 1.
|
|
84
|
+
rubocop (>= 1.52.0, < 2.0)
|
|
85
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
|
89
86
|
rubocop-rake (0.6.0)
|
|
90
87
|
rubocop (~> 1.0)
|
|
91
|
-
rubocop-rspec (2.
|
|
92
|
-
rubocop (~> 1.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
rubocop-rspec (3.2.0)
|
|
89
|
+
rubocop (~> 1.61)
|
|
90
|
+
rubocop-rspec_rails (2.30.0)
|
|
91
|
+
rubocop (~> 1.61)
|
|
92
|
+
rubocop-rspec (~> 3, >= 3.0.1)
|
|
93
|
+
ruby-progressbar (1.13.0)
|
|
94
|
+
simplecov (0.22.0)
|
|
96
95
|
docile (~> 1.1)
|
|
97
96
|
simplecov-html (~> 0.11)
|
|
98
97
|
simplecov_json_formatter (~> 0.1)
|
|
99
|
-
simplecov-html (0.
|
|
98
|
+
simplecov-html (0.13.1)
|
|
100
99
|
simplecov-lcov (0.8.0)
|
|
101
100
|
simplecov_json_formatter (0.1.4)
|
|
102
|
-
|
|
103
|
-
activesupport (>= 5.1)
|
|
104
|
-
language_server-protocol (>= 3.15, < 4.0)
|
|
105
|
-
listen (~> 3.0)
|
|
106
|
-
parallel (>= 1.0.0)
|
|
107
|
-
parser (>= 3.0)
|
|
108
|
-
rainbow (>= 2.2.2, < 4.0)
|
|
109
|
-
rbs (>= 2.3.2)
|
|
110
|
-
terminal-table (>= 2, < 4)
|
|
111
|
-
symbiont-ruby (0.7.0)
|
|
112
|
-
terminal-table (3.0.2)
|
|
113
|
-
unicode-display_width (>= 1.1.1, < 3)
|
|
114
|
-
thor (1.2.1)
|
|
115
|
-
tzinfo (2.0.4)
|
|
101
|
+
tzinfo (2.0.6)
|
|
116
102
|
concurrent-ruby (~> 1.0)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
symbiont-ruby
|
|
120
|
-
unicode-display_width (2.1.0)
|
|
103
|
+
unicode-display_width (2.6.0)
|
|
104
|
+
zeitwerk (2.7.1)
|
|
121
105
|
|
|
122
106
|
PLATFORMS
|
|
123
|
-
arm64-darwin-
|
|
107
|
+
arm64-darwin-24
|
|
108
|
+
ruby
|
|
124
109
|
|
|
125
110
|
DEPENDENCIES
|
|
126
|
-
|
|
111
|
+
activesupport (~> 6, < 8)
|
|
112
|
+
armitage-rubocop (~> 1.59)
|
|
127
113
|
bundler (>= 1)
|
|
128
|
-
|
|
129
|
-
ci-helper (~> 0.5)
|
|
130
|
-
pry (~> 0.14)
|
|
114
|
+
pry (~> 0.15)
|
|
131
115
|
qonfig!
|
|
132
116
|
rake (>= 13)
|
|
133
|
-
rspec (~> 3.
|
|
134
|
-
simplecov (~> 0.
|
|
117
|
+
rspec (~> 3.2)
|
|
118
|
+
simplecov (~> 0.22)
|
|
135
119
|
simplecov-lcov (~> 0.8)
|
|
136
|
-
steep (~> 1.0)
|
|
137
120
|
|
|
138
121
|
BUNDLED WITH
|
|
139
|
-
2.
|
|
122
|
+
2.5.23
|
|
@@ -41,10 +41,14 @@ class Qonfig::Commands::Definition::AddNestedOption < Qonfig::Commands::Base
|
|
|
41
41
|
#
|
|
42
42
|
# @api private
|
|
43
43
|
# @since 0.1.0
|
|
44
|
+
# @version 0.31.0
|
|
44
45
|
def call(data_set, settings)
|
|
45
46
|
nested_settings = nested_data_set_klass.new.settings
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
# NOTE:
|
|
49
|
+
# The nested instance is wired into the host's mutation-callback chain by
|
|
50
|
+
# Qonfig::Settings#__define_setting__ itself (see __store_setting_value__),
|
|
51
|
+
# so every nested-settings entry point shares one wiring path.
|
|
48
52
|
settings.__define_setting__(key, nested_settings)
|
|
49
53
|
end
|
|
50
54
|
end
|
|
@@ -8,37 +8,37 @@ module Qonfig::Commands::Definition::LoadFromENV::ValueConverter
|
|
|
8
8
|
#
|
|
9
9
|
# @api private
|
|
10
10
|
# @since 0.2.0
|
|
11
|
-
INTEGER_PATTERN = /\A\d+\z
|
|
11
|
+
INTEGER_PATTERN = /\A\d+\z/
|
|
12
12
|
|
|
13
13
|
# @return [Regexp]
|
|
14
14
|
#
|
|
15
15
|
# @api private
|
|
16
16
|
# @since 0.2.0
|
|
17
|
-
FLOAT_PATTERN = /\A\d+\.\d+\z
|
|
17
|
+
FLOAT_PATTERN = /\A\d+\.\d+\z/
|
|
18
18
|
|
|
19
19
|
# @return [Regexp]
|
|
20
20
|
#
|
|
21
21
|
# @api private
|
|
22
22
|
# @since 0.2.0
|
|
23
|
-
TRUE_PATTERN = /\A(t|true)\z/i
|
|
23
|
+
TRUE_PATTERN = /\A(t|true)\z/i
|
|
24
24
|
|
|
25
25
|
# @return [Regexp]
|
|
26
26
|
#
|
|
27
27
|
# @api private
|
|
28
28
|
# @since 0.2.0
|
|
29
|
-
FALSE_PATTERN = /\A(f|false)\z/i
|
|
29
|
+
FALSE_PATTERN = /\A(f|false)\z/i
|
|
30
30
|
|
|
31
31
|
# @return [Regexp]
|
|
32
32
|
#
|
|
33
33
|
# @api private
|
|
34
34
|
# @since 0.2.0
|
|
35
|
-
ARRAY_PATTERN = /\A[^'"].*\s*,\s*.*[^'"]\z
|
|
35
|
+
ARRAY_PATTERN = /\A[^'"].*\s*,\s*.*[^'"]\z/
|
|
36
36
|
|
|
37
37
|
# @return [Regexp]
|
|
38
38
|
#
|
|
39
39
|
# @api private
|
|
40
40
|
# @since 0.2.0
|
|
41
|
-
QUOTED_STRING_PATTERN = /\A['"].*['"]\z
|
|
41
|
+
QUOTED_STRING_PATTERN = /\A['"].*['"]\z/
|
|
42
42
|
|
|
43
43
|
class << self
|
|
44
44
|
# @param env_data [Hash]
|
|
@@ -48,10 +48,14 @@ class Qonfig::Commands::Definition::ReDefineOption < Qonfig::Commands::Base
|
|
|
48
48
|
#
|
|
49
49
|
# @api private
|
|
50
50
|
# @since 0.20.0
|
|
51
|
+
# @version 0.31.0
|
|
51
52
|
def call(data_set, settings)
|
|
52
53
|
if nested_data_set_klass
|
|
53
54
|
nested_settings = nested_data_set_klass.new.settings
|
|
54
|
-
|
|
55
|
+
# NOTE:
|
|
56
|
+
# The nested instance is wired into the host's mutation-callback chain by
|
|
57
|
+
# Qonfig::Settings#__define_setting__ itself (see __store_setting_value__),
|
|
58
|
+
# so every nested-settings entry point shares one wiring path.
|
|
55
59
|
settings.__define_setting__(key, nested_settings, with_redefinition: true)
|
|
56
60
|
else
|
|
57
61
|
settings.__define_setting__(key, value, with_redefinition: true)
|
|
@@ -96,7 +96,7 @@ class Qonfig::Commands::Instantiation::ValuesFile < Qonfig::Commands::Base
|
|
|
96
96
|
def call(data_set, settings)
|
|
97
97
|
settings_values = load_settings_values
|
|
98
98
|
return unless settings_values
|
|
99
|
-
settings_values =
|
|
99
|
+
settings_values = settings_values[expose.to_sym] || settings_values[expose.to_s] if expose
|
|
100
100
|
data_set.configure(settings_values) if settings_values
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -156,11 +156,9 @@ class Qonfig::Commands::Instantiation::ValuesFile < Qonfig::Commands::Base
|
|
|
156
156
|
# @version 0.22.0
|
|
157
157
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
158
158
|
def prevent_incompatible_attributes!(file_path, format, strict, expose)
|
|
159
|
-
unless (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
file_path == SELF_LOCATED_FILE_DEFINITION
|
|
163
|
-
)
|
|
159
|
+
unless file_path.is_a?(String) ||
|
|
160
|
+
file_path.is_a?(Pathname) ||
|
|
161
|
+
file_path == SELF_LOCATED_FILE_DEFINITION
|
|
164
162
|
raise Qonfig::ArgumentError, 'Incorrect file path'
|
|
165
163
|
end
|
|
166
164
|
|
|
@@ -43,9 +43,9 @@ class Qonfig::Imports::DirectKey < Qonfig::Imports::Abstract
|
|
|
43
43
|
raise(
|
|
44
44
|
Qonfig::UnknownSettingError,
|
|
45
45
|
"Setting with <#{key_matcher.scope_pattern}> key does not exist!"
|
|
46
|
-
) unless
|
|
46
|
+
) unless imported_config.keys(all_variants: true).any? do |setting_key|
|
|
47
47
|
key_matcher.match?(setting_key)
|
|
48
|
-
end || key_matcher.generic?
|
|
48
|
+
end || key_matcher.generic?
|
|
49
49
|
|
|
50
50
|
imported_config.keys(all_variants: true).each do |setting_key|
|
|
51
51
|
next unless key_matcher.match?(setting_key)
|
|
@@ -69,13 +69,13 @@ class Qonfig::Imports::DirectKey < Qonfig::Imports::Abstract
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
define_method("#{access_method_name}?") do
|
|
72
|
+
define_method(:"#{access_method_name}?") do
|
|
73
73
|
# NOTE: based on Qonfig::Settings#__define_option_predicate__ realization
|
|
74
74
|
!!imported_config[setting_key]
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
if accessor
|
|
78
|
-
define_method("#{access_method_name}=") do |value|
|
|
78
|
+
define_method(:"#{access_method_name}=") do |value|
|
|
79
79
|
imported_config[setting_key] = value
|
|
80
80
|
end
|
|
81
81
|
end
|
|
@@ -49,9 +49,9 @@ class Qonfig::Imports::Mappings < Qonfig::Imports::Abstract
|
|
|
49
49
|
raise(
|
|
50
50
|
Qonfig::UnknownSettingError,
|
|
51
51
|
"Setting with <#{key_matcher.scope_pattern}> key does not exist!"
|
|
52
|
-
) unless
|
|
52
|
+
) unless imported_config.keys(all_variants: true).any? do |setting_key|
|
|
53
53
|
key_matcher.match?(setting_key)
|
|
54
|
-
end || key_matcher.generic?
|
|
54
|
+
end || key_matcher.generic?
|
|
55
55
|
|
|
56
56
|
imported_config.keys(all_variants: true).each do |setting_key|
|
|
57
57
|
next unless key_matcher.match?(setting_key)
|
|
@@ -74,13 +74,13 @@ class Qonfig::Imports::Mappings < Qonfig::Imports::Abstract
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
define_method("#{mapped_method_name}?") do
|
|
77
|
+
define_method(:"#{mapped_method_name}?") do
|
|
78
78
|
# NOTE: based on Qonfig::Settings#__define_option_predicate__ realization
|
|
79
79
|
!!imported_config[setting_key]
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
if accessor
|
|
83
|
-
define_method("#{mapped_method_name}=") do |value|
|
|
83
|
+
define_method(:"#{mapped_method_name}=") do |value|
|
|
84
84
|
imported_config[setting_key] = value
|
|
85
85
|
end
|
|
86
86
|
end
|
data/lib/qonfig/plugins.rb
CHANGED
|
@@ -31,10 +31,8 @@ module Qonfig::Plugins
|
|
|
31
31
|
# @since 0.19.0
|
|
32
32
|
def loaded_plugins
|
|
33
33
|
thread_safe do
|
|
34
|
-
plugin_registry.
|
|
35
|
-
plugin_module.loaded?
|
|
36
|
-
end.map do |plugin_name, _plugin_module|
|
|
37
|
-
plugin_name
|
|
34
|
+
plugin_registry.filter_map do |plugin_name, plugin_module|
|
|
35
|
+
plugin_name if plugin_module.loaded?
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
end
|
|
@@ -35,6 +35,15 @@ class Qonfig::Settings::Callbacks
|
|
|
35
35
|
thread_safe { callbacks << callback }
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# @param callback [Proc, Qonfig::Settings::Callbacks, #call]
|
|
39
|
+
# @return [void]
|
|
40
|
+
#
|
|
41
|
+
# @api private
|
|
42
|
+
# @since 0.31.0
|
|
43
|
+
def prepend(callback)
|
|
44
|
+
thread_safe { callbacks.unshift(callback) }
|
|
45
|
+
end
|
|
46
|
+
|
|
38
47
|
private
|
|
39
48
|
|
|
40
49
|
# @return [Array<Proc>]
|
data/lib/qonfig/settings.rb
CHANGED
|
@@ -14,13 +14,13 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
14
14
|
#
|
|
15
15
|
# @api private
|
|
16
16
|
# @since 0.11.0
|
|
17
|
-
BASIC_SETTING_KEY_TRANSFORMER =
|
|
17
|
+
BASIC_SETTING_KEY_TRANSFORMER = proc { |value| value }.freeze
|
|
18
18
|
|
|
19
19
|
# @return [Proc]
|
|
20
20
|
#
|
|
21
21
|
# @api private
|
|
22
22
|
# @since 0.11.0
|
|
23
|
-
BASIC_SETTING_VALUE_TRANSFORMER =
|
|
23
|
+
BASIC_SETTING_VALUE_TRANSFORMER = proc { |value| value }.freeze
|
|
24
24
|
|
|
25
25
|
# @return [Boolean]
|
|
26
26
|
#
|
|
@@ -48,10 +48,24 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
48
48
|
|
|
49
49
|
# @api private
|
|
50
50
|
# @since 0.1.0
|
|
51
|
+
# @version 0.31.0
|
|
51
52
|
def initialize(__mutation_callbacks__)
|
|
52
53
|
@__options__ = {}
|
|
53
54
|
@__lock__ = Lock.new
|
|
54
55
|
@__mutation_callbacks__ = __mutation_callbacks__
|
|
56
|
+
@__deep_access_cache__ = {}
|
|
57
|
+
|
|
58
|
+
# NOTE:
|
|
59
|
+
# Register a cache invalidator inside our own mutation callbacks.
|
|
60
|
+
# Nested settings add their parent's callbacks object to their own callbacks
|
|
61
|
+
# (see #__store_setting_value__), so a mutation of any nested config propagates
|
|
62
|
+
# upwards through the chain and drops the cached digging results on EACH config
|
|
63
|
+
# instance along the way ("matryoshka" cache invalidation).
|
|
64
|
+
#
|
|
65
|
+
# It is prepended so that it runs BEFORE the validation callback — full
|
|
66
|
+
# data-set validators read setting values back through #__dig__, so the
|
|
67
|
+
# cache must already be dropped by the time they run.
|
|
68
|
+
@__mutation_callbacks__.prepend(proc { __invalidate_deep_access_cache__ })
|
|
55
69
|
end
|
|
56
70
|
|
|
57
71
|
# @param block [Proc]
|
|
@@ -93,23 +107,21 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
93
107
|
#
|
|
94
108
|
# @api private
|
|
95
109
|
# @since 0.1.0
|
|
96
|
-
# @version 0.
|
|
110
|
+
# @version 0.31.0
|
|
97
111
|
def __define_setting__(key, value, with_redefinition: false) # rubocop:disable Metrics/AbcSize
|
|
98
112
|
__lock__.thread_safe_definition do
|
|
99
113
|
key = __indifferently_accessable_option_key__(key)
|
|
100
114
|
|
|
101
115
|
__prevent_core_method_intersection__(key)
|
|
102
116
|
|
|
103
|
-
# rubocop:disable Lint/DuplicateBranch
|
|
104
117
|
case
|
|
105
118
|
when with_redefinition || !__options__.key?(key)
|
|
106
|
-
|
|
119
|
+
__store_setting_value__(key, value)
|
|
107
120
|
when __is_a_setting__(__options__[key]) && __is_a_setting__(value)
|
|
108
121
|
__options__[key].__append_settings__(value)
|
|
109
122
|
else
|
|
110
|
-
|
|
123
|
+
__store_setting_value__(key, value)
|
|
111
124
|
end
|
|
112
|
-
# rubocop:enable Lint/DuplicateBranch
|
|
113
125
|
|
|
114
126
|
__define_option_reader__(key)
|
|
115
127
|
__define_option_writer__(key)
|
|
@@ -177,16 +189,21 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
177
189
|
#
|
|
178
190
|
# @api private
|
|
179
191
|
# @since 0.2.0
|
|
192
|
+
# @version 0.31.0
|
|
180
193
|
def __dig__(*keys)
|
|
181
194
|
__lock__.thread_safe_access do
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
# NOTE:
|
|
196
|
+
# Resolving a dot-notated/nested key (config['a.b.c.d']) is expensive:
|
|
197
|
+
# it splits and re-joins string fragments and digs through every nested
|
|
198
|
+
# config instance on each access. We memoize the resolved value per key
|
|
199
|
+
# set; the cache is dropped on any mutation (see __invalidate...).
|
|
200
|
+
cache = @__deep_access_cache__
|
|
201
|
+
cache_key = __deep_access_cache_key__(keys)
|
|
202
|
+
|
|
203
|
+
if cache.key?(cache_key)
|
|
204
|
+
cache[cache_key]
|
|
205
|
+
else
|
|
206
|
+
__resolve_deep_access__(*keys).tap { |value| cache[cache_key] = value }
|
|
190
207
|
end
|
|
191
208
|
end
|
|
192
209
|
end
|
|
@@ -577,6 +594,75 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
577
594
|
__invoke_mutation_callbacks__
|
|
578
595
|
end
|
|
579
596
|
|
|
597
|
+
# @param key [String]
|
|
598
|
+
# @param value [Object]
|
|
599
|
+
# @return [void]
|
|
600
|
+
#
|
|
601
|
+
# @api private
|
|
602
|
+
# @since 0.31.0
|
|
603
|
+
def __store_setting_value__(key, value)
|
|
604
|
+
__options__[key] = value
|
|
605
|
+
|
|
606
|
+
# NOTE:
|
|
607
|
+
# A nested settings instance can arrive here "foreign" — built under another
|
|
608
|
+
# data set and merged into us by reference (compose / load_from_* / expose_* /
|
|
609
|
+
# option re-definition). Its mutation callbacks would then still point only at
|
|
610
|
+
# the discarded foreign parent, so a mutation of the nested config would never
|
|
611
|
+
# invalidate OUR deep-access cache (=> stale reads). Wire the nested instance
|
|
612
|
+
# into our callback chain on assignment so invalidation always cascades upwards
|
|
613
|
+
# ("matryoshka"), regardless of how the nested settings instance got here.
|
|
614
|
+
value.__mutation_callbacks__.add(__mutation_callbacks__) if __is_a_setting__(value)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# @param keys [Array<String, Symbol>]
|
|
618
|
+
# @return [Object]
|
|
619
|
+
#
|
|
620
|
+
# @raise [Qonfig::UnknownSettingError]
|
|
621
|
+
#
|
|
622
|
+
# @api private
|
|
623
|
+
# @since 0.31.0
|
|
624
|
+
def __resolve_deep_access__(*keys)
|
|
625
|
+
__deep_access__(*keys)
|
|
626
|
+
rescue Qonfig::UnknownSettingError
|
|
627
|
+
if keys.size == 1
|
|
628
|
+
__deep_access__(*__parse_dot_notated_key__(keys.first))
|
|
629
|
+
else
|
|
630
|
+
raise
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# @param keys [Array<String, Symbol>]
|
|
635
|
+
# @return [String, Array<String>]
|
|
636
|
+
#
|
|
637
|
+
# @api private
|
|
638
|
+
# @since 0.31.0
|
|
639
|
+
def __deep_access_cache_key__(keys)
|
|
640
|
+
# NOTE:
|
|
641
|
+
# Stringify keys so that symbol/string variants share a single cache slot
|
|
642
|
+
# (indifferent access), while preserving key boundaries — ['a.b', 'c'] and
|
|
643
|
+
# ['a', 'b', 'c'] resolve differently and must not collide. The single-key
|
|
644
|
+
# case (the dominant config['a.b.c'] form) avoids an extra array allocation.
|
|
645
|
+
(keys.size == 1) ? keys.first.to_s : keys.map(&:to_s)
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# @return [void]
|
|
649
|
+
#
|
|
650
|
+
# @api private
|
|
651
|
+
# @since 0.31.0
|
|
652
|
+
def __invalidate_deep_access_cache__
|
|
653
|
+
# NOTE:
|
|
654
|
+
# Reassign (instead of #clear) so concurrent readers never observe a
|
|
655
|
+
# half-cleared hash — invalidation can propagate upwards from a nested
|
|
656
|
+
# mutation WITHOUT holding our access lock.
|
|
657
|
+
#
|
|
658
|
+
# The reassignment must be UNCONDITIONAL. A `unless empty?` guard would skip
|
|
659
|
+
# the swap during the window where a concurrent reader has already resolved a
|
|
660
|
+
# now-stale value but has not yet written it into the (still-empty) cache —
|
|
661
|
+
# the stale write would then survive and be served forever. Rebinding the ivar
|
|
662
|
+
# to a fresh hash makes such a late write land in an orphaned hash instead.
|
|
663
|
+
@__deep_access_cache__ = {}
|
|
664
|
+
end
|
|
665
|
+
|
|
580
666
|
# @param keys [Array<Symbol, String>]
|
|
581
667
|
# @return [Object]
|
|
582
668
|
#
|
|
@@ -600,7 +686,7 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
600
686
|
rest_keys = Array(keys[(key_parts_slice_boundary + 1)..-1])
|
|
601
687
|
break
|
|
602
688
|
rescue Qonfig::UnknownSettingError => error
|
|
603
|
-
key_parts_boundary == key_parts_slice_boundary ? raise(error) : next
|
|
689
|
+
(key_parts_boundary == key_parts_slice_boundary) ? raise(error) : next
|
|
604
690
|
end
|
|
605
691
|
end
|
|
606
692
|
|
|
@@ -771,7 +857,7 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
771
857
|
# @api private
|
|
772
858
|
# @since 0.13.0
|
|
773
859
|
def __define_option_writer__(key)
|
|
774
|
-
define_singleton_method("#{key}=") do |value|
|
|
860
|
+
define_singleton_method(:"#{key}=") do |value|
|
|
775
861
|
self.[]=(key, value)
|
|
776
862
|
end
|
|
777
863
|
end
|
|
@@ -782,7 +868,7 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
782
868
|
# @api private
|
|
783
869
|
# @since 0.13.0
|
|
784
870
|
def __define_option_predicate__(key)
|
|
785
|
-
define_singleton_method("#{key}?") do
|
|
871
|
+
define_singleton_method(:"#{key}?") do
|
|
786
872
|
!!self.[](key)
|
|
787
873
|
end
|
|
788
874
|
end
|
|
@@ -114,8 +114,8 @@ class Qonfig::Validation::Building::InstanceBuilder::AttributesConsistency
|
|
|
114
114
|
)
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
if (
|
|
118
|
-
(predefined_validator && (runtime_validation_method || validation_logic))
|
|
117
|
+
if (runtime_validation_method && validation_logic) ||
|
|
118
|
+
(predefined_validator && (runtime_validation_method || validation_logic))
|
|
119
119
|
raise(
|
|
120
120
|
Qonfig::ValidatorArgumentError,
|
|
121
121
|
'Incosistent validation (you should use: dataset method OR proc OR predefined validator)'
|
data/lib/qonfig/version.rb
CHANGED