qonfig 0.30.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 +2 -0
- data/CHANGELOG.md +14 -2
- data/Gemfile.lock +1 -1
- data/README.md +3 -2
- data/gemfiles/with_external_deps.gemfile.lock +1 -1
- data/gemfiles/without_external_deps.gemfile.lock +1 -1
- data/lib/qonfig/commands/definition/add_nested_option.rb +5 -1
- data/lib/qonfig/commands/definition/re_define_option.rb +5 -1
- data/lib/qonfig/settings/callbacks.rb +9 -0
- data/lib/qonfig/settings.rb +99 -11
- data/lib/qonfig/version.rb +1 -1
- data/spec/features/nested_access_caching_spec.rb +134 -0
- data/spec/features/run_code_with_temporary_settings_spec.rb +70 -68
- data/spec/features/save_to_file/save_to_json_spec.rb +38 -16
- metadata +4 -7
- data/.jrubyrc +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a51d2be5daa99fe68c0a7b3db5a802409a8b02e3ca2fe62abca61c0d1f7f0cf
|
|
4
|
+
data.tar.gz: a54a12a2fa42abd3b7025b1427e1939582063a5ccc0f4d3648fe8a336361ce52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 915fb311e7af429433f218b04b7162c4691d7208329ae6d104990952a417c59ce6cdc55721f3393b435aa2e07642000c4afdd7902f3f20a018c287661f4462c5
|
|
7
|
+
data.tar.gz: 73f06741831e901ea385c1712d6a56f42e7a780809d5fa8dca848a6025f95fa24ddd84c3a4e80bb2a03934f2c55c65e43827537114801d16d1780b4e7c34572e
|
data/.github/workflows/build.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
|
3
3
|
|
|
4
|
+
## [Unreleased]
|
|
5
|
+
### Added
|
|
6
|
+
- Support for `truffleruby`;
|
|
7
|
+
|
|
8
|
+
## [0.31.0] - 2026-06-24
|
|
9
|
+
### Changed
|
|
10
|
+
- Performance: nested / dot-notated reads (`config['a.b.c.d.e.f']`, `config.dig(...)`)
|
|
11
|
+
are now memoized inside each config instance instead of re-splitting and re-digging
|
|
12
|
+
the key on every access. The cache is dropped on any mutation and the invalidation
|
|
13
|
+
cascades up through all parent config instances ("matryoshka" cache invalidation),
|
|
14
|
+
so nested configs (which are separate instances) never serve stale values.
|
|
15
|
+
|
|
4
16
|
## [0.30.0] - 2024-12-14
|
|
5
17
|
### Changed
|
|
6
18
|
- Updated development dependencies;
|
|
7
19
|
- Updated code style;
|
|
8
|
-
- Dropped EOL rubies support. Now Qonfig supports
|
|
20
|
+
- Dropped EOL rubies support. Now Qonfig supports rubies started from `>= 3.1`;
|
|
9
21
|
- CI: returned separated plugin-related specs (`bin/rspec -w` + `bin/rspec -n` (with- and without- plugins respectively));
|
|
10
22
|
- CI: dropped coveralls (temporary);
|
|
11
23
|
- CI: droppet java-based rubies support (temporary);
|
|
12
|
-
- Simplified CI;
|
|
24
|
+
- Simplified CI (temporary);
|
|
13
25
|
|
|
14
26
|
## [0.29.0] - 2024-05-06
|
|
15
27
|
### Added
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -3329,9 +3329,9 @@ Qonfig.plugin(:vault)
|
|
|
3329
3329
|
|
|
3330
3330
|
## Roadmap
|
|
3331
3331
|
|
|
3332
|
-
- **General**:
|
|
3333
|
-
- documentation rework;
|
|
3334
3332
|
- **Major**:
|
|
3333
|
+
- `on_change`-hooks (in order to proviade the possibility to change something like "re-initialize redis connection pool on each redis config changement" and so on);
|
|
3334
|
+
- Runtime Configuration (with in-memory cache);
|
|
3335
3335
|
- support for Rails-like secrets;
|
|
3336
3336
|
- support for persistent data storages (we want to store configs in multiple databases and files);
|
|
3337
3337
|
- rails plugin;
|
|
@@ -3347,6 +3347,7 @@ Qonfig.plugin(:vault)
|
|
|
3347
3347
|
- File geneartors (.rb-files with a pre-filled code (and (maybe) with a pre-generated yaml/json/etc files));
|
|
3348
3348
|
- Setting value changement subscriptions and callbacks;
|
|
3349
3349
|
- CI rework (new configs, new steps, no strange dependencies);
|
|
3350
|
+
- CI: thread-safety specs for `thruffleruby`;
|
|
3350
3351
|
|
|
3351
3352
|
## Build
|
|
3352
3353
|
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -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
|
@@ -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,7 +107,7 @@ 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)
|
|
@@ -102,11 +116,11 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
102
116
|
|
|
103
117
|
case
|
|
104
118
|
when with_redefinition || !__options__.key?(key)
|
|
105
|
-
|
|
119
|
+
__store_setting_value__(key, value)
|
|
106
120
|
when __is_a_setting__(__options__[key]) && __is_a_setting__(value)
|
|
107
121
|
__options__[key].__append_settings__(value)
|
|
108
122
|
else
|
|
109
|
-
|
|
123
|
+
__store_setting_value__(key, value)
|
|
110
124
|
end
|
|
111
125
|
|
|
112
126
|
__define_option_reader__(key)
|
|
@@ -175,16 +189,21 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
175
189
|
#
|
|
176
190
|
# @api private
|
|
177
191
|
# @since 0.2.0
|
|
192
|
+
# @version 0.31.0
|
|
178
193
|
def __dig__(*keys)
|
|
179
194
|
__lock__.thread_safe_access do
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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 }
|
|
188
207
|
end
|
|
189
208
|
end
|
|
190
209
|
end
|
|
@@ -575,6 +594,75 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
|
|
|
575
594
|
__invoke_mutation_callbacks__
|
|
576
595
|
end
|
|
577
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
|
+
|
|
578
666
|
# @param keys [Array<Symbol, String>]
|
|
579
667
|
# @return [Object]
|
|
580
668
|
#
|
data/lib/qonfig/version.rb
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe 'Nested access caching (with matryoshka cache invalidation)' do
|
|
4
|
+
let(:config) do
|
|
5
|
+
Qonfig::DataSet.build do
|
|
6
|
+
setting :a do
|
|
7
|
+
setting :b do
|
|
8
|
+
setting :c do
|
|
9
|
+
setting :d, 1
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
setting :flat, 'value'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
specify 'repeated dot-notated / nested reads return the same (memoized) result' do
|
|
19
|
+
expect(config['a.b.c.d']).to eq(1)
|
|
20
|
+
expect(config['a.b.c.d']).to eq(1)
|
|
21
|
+
|
|
22
|
+
nested = config['a.b.c']
|
|
23
|
+
expect(nested).to be_a(Qonfig::Settings)
|
|
24
|
+
# the very same nested settings instance is returned from the cache
|
|
25
|
+
expect(config['a.b.c']).to equal(nested)
|
|
26
|
+
expect(config.dig(:a, :b, :c)).to equal(nested)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
specify 'symbol and string key variants share the cache (indifferent access)' do
|
|
30
|
+
expect(config[:a][:b][:c][:d]).to eq(1)
|
|
31
|
+
expect(config['a.b.c.d']).to eq(1)
|
|
32
|
+
expect(config.dig('a', :b, 'c', :d)).to eq(1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
specify 'assigning a value invalidates the cache' do
|
|
36
|
+
expect(config['a.b.c.d']).to eq(1)
|
|
37
|
+
|
|
38
|
+
config['a.b.c.d'] = 2
|
|
39
|
+
expect(config['a.b.c.d']).to eq(2)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
specify 'mutating a nested config instance invalidates every parent cache (matryoshka)' do
|
|
43
|
+
# warm up caches on every level
|
|
44
|
+
expect(config['a.b.c.d']).to eq(1)
|
|
45
|
+
expect(config['a.b.c']['d']).to eq(1)
|
|
46
|
+
|
|
47
|
+
# mutate through a directly-grabbed nested instance
|
|
48
|
+
nested = config['a.b.c']
|
|
49
|
+
nested['d'] = 99
|
|
50
|
+
|
|
51
|
+
# the mutation must be visible from the root through the (now stale) cache
|
|
52
|
+
expect(config['a.b.c.d']).to eq(99)
|
|
53
|
+
expect(config['a.b']['c.d']).to eq(99)
|
|
54
|
+
expect(nested['d']).to eq(99)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
specify 'a flat value mutation invalidates the cache' do
|
|
58
|
+
expect(config['flat']).to eq('value')
|
|
59
|
+
config['flat'] = 'changed'
|
|
60
|
+
expect(config['flat']).to eq('changed')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
specify '#clear! invalidates the cache' do
|
|
64
|
+
expect(config['a.b.c.d']).to eq(1)
|
|
65
|
+
config.clear!
|
|
66
|
+
expect(config['a.b.c.d']).to be_nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
specify 'full data-set validators observe fresh values after mutation (no stale cache)' do
|
|
70
|
+
validated_config = Class.new(Qonfig::DataSet) do
|
|
71
|
+
setting :nested do
|
|
72
|
+
setting :flag, :yes
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
validate { settings.nested.flag.is_a?(Symbol) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
instance = validated_config.new
|
|
79
|
+
# warm the cache through the validator-read path
|
|
80
|
+
expect(instance.settings.nested.flag).to eq(:yes)
|
|
81
|
+
|
|
82
|
+
expect { instance.settings.nested.flag = 123 }.to raise_error(Qonfig::ValidationError)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
specify 'composed nested settings invalidate the host cache on mutation' do
|
|
86
|
+
shared = Class.new(Qonfig::DataSet) do
|
|
87
|
+
setting :s do
|
|
88
|
+
setting :v, 1
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
host = Qonfig::DataSet.build { compose shared }
|
|
93
|
+
|
|
94
|
+
expect(host['s.v']).to eq(1) # warm up the cache through the host
|
|
95
|
+
host['s.v'] = 88
|
|
96
|
+
# the nested instance was merged in "by reference"; mutating it must still
|
|
97
|
+
# cascade invalidation up to the host (otherwise the host serves a stale 1)
|
|
98
|
+
expect(host['s.v']).to eq(88)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
specify 're-defined nested settings invalidate the cache on mutation' do
|
|
102
|
+
redefined_config = Class.new(Qonfig::DataSet) do
|
|
103
|
+
setting :s do
|
|
104
|
+
setting :v, 1
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
re_setting :s do
|
|
108
|
+
setting :v, 10
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
instance = redefined_config.new
|
|
113
|
+
|
|
114
|
+
expect(instance['s.v']).to eq(10) # warm up the cache
|
|
115
|
+
instance['s.v'] = 77
|
|
116
|
+
expect(instance['s.v']).to eq(77)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
specify 'cache invalidation always swaps in a fresh hash, even when already empty' do
|
|
120
|
+
# Guards the lock-free invalidation contract: invalidation may propagate up
|
|
121
|
+
# from a nested mutation without holding our access lock, so the swap must be
|
|
122
|
+
# unconditional. A `unless empty?` guard would let a concurrent reader that has
|
|
123
|
+
# resolved a stale value (but not yet written it) persist that value forever.
|
|
124
|
+
settings = config.settings
|
|
125
|
+
empty_cache = settings.instance_variable_get(:@__deep_access_cache__)
|
|
126
|
+
expect(empty_cache).to be_empty
|
|
127
|
+
|
|
128
|
+
settings.send(:__invalidate_deep_access_cache__)
|
|
129
|
+
|
|
130
|
+
swapped_cache = settings.instance_variable_get(:@__deep_access_cache__)
|
|
131
|
+
expect(swapped_cache).to be_empty
|
|
132
|
+
expect(swapped_cache).not_to equal(empty_cache)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -22,83 +22,85 @@ describe 'Run code with temporary settings' do
|
|
|
22
22
|
expect(config.settings.api.login).to eq('D@iVeR')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
setting :
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
unless RUBY_ENGINE.include?('truffleruby')
|
|
26
|
+
specify 'thread-safety' do
|
|
27
|
+
config = Qonfig::DataSet.build do
|
|
28
|
+
setting :api do
|
|
29
|
+
setting :token, 'test123'
|
|
30
|
+
setting :login, 'D@iVeR'
|
|
31
|
+
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
setting :credentials do
|
|
34
|
+
setting :user, 'admin'
|
|
35
|
+
setting :password, '1234asdf'
|
|
36
|
+
end
|
|
35
37
|
end
|
|
36
|
-
end
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
ThreadGroup.new.tap do |thread_group|
|
|
40
|
+
10.times do
|
|
41
|
+
thread_group.add(Thread.new do
|
|
42
|
+
# NOTE: change settings temporary
|
|
43
|
+
config.with(api: { token: '777555' }, credentials: { password: 'test123' }) do
|
|
44
|
+
config.settings.credentials.user = 'nimda'
|
|
45
|
+
end
|
|
46
|
+
end)
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
thread_group.add(Thread.new do
|
|
49
|
+
# NOTE: settings are not changed :)
|
|
50
|
+
expect(config.settings.credentials.user).to eq('admin')
|
|
51
|
+
expect(config.settings.credentials.password).to eq('1234asdf')
|
|
52
|
+
expect(config.settings.api.token).to eq('test123')
|
|
53
|
+
expect(config.settings.api.login).to eq('D@iVeR')
|
|
54
|
+
end)
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
thread_group.add(Thread.new do
|
|
57
|
+
# NOTE: change settings temporary
|
|
58
|
+
config.with do
|
|
59
|
+
config.settings.api.login = 'provider'
|
|
60
|
+
config.settings.api.token = 'super_puper_123'
|
|
61
|
+
end
|
|
62
|
+
end)
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
thread_group.add(Thread.new do
|
|
65
|
+
# NOTE: settings are not changed :)
|
|
66
|
+
expect(config.settings.credentials.user).to eq('admin')
|
|
67
|
+
expect(config.settings.credentials.password).to eq('1234asdf')
|
|
68
|
+
expect(config.settings.api.token).to eq('test123')
|
|
69
|
+
expect(config.settings.api.login).to eq('D@iVeR')
|
|
70
|
+
end)
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
thread_group.add(Thread.new do
|
|
73
|
+
config.with(api: { login: '0exp' }, credentials: { user: 'D@iVeR' }) do
|
|
74
|
+
config.settings.api.token = 'kekpek123'
|
|
75
|
+
config.settings.credentials.password = 'admin'
|
|
76
|
+
end
|
|
77
|
+
end)
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
thread_group.add(Thread.new do
|
|
80
|
+
# NOTE: settings are not changed :)
|
|
81
|
+
expect(config.settings.credentials.user).to eq('admin')
|
|
82
|
+
expect(config.settings.credentials.password).to eq('1234asdf')
|
|
83
|
+
expect(config.settings.api.token).to eq('test123')
|
|
84
|
+
expect(config.settings.api.login).to eq('D@iVeR')
|
|
85
|
+
end)
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
thread_group.add(Thread.new do
|
|
88
|
+
# NOTE: change settings temporary
|
|
89
|
+
config.with(api: { login: 'mobile_legends' }, credentials: { user: 'dota2' }) do
|
|
90
|
+
config.settings.api.token = 'league_of_legends'
|
|
91
|
+
config.settings.credentials.password = 'overwatch'
|
|
92
|
+
end
|
|
93
|
+
end)
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
thread_group.add(Thread.new do
|
|
96
|
+
# NOTE: settings are not changed :)
|
|
97
|
+
expect(config.settings.credentials.user).to eq('admin')
|
|
98
|
+
expect(config.settings.credentials.password).to eq('1234asdf')
|
|
99
|
+
expect(config.settings.api.token).to eq('test123')
|
|
100
|
+
expect(config.settings.api.login).to eq('D@iVeR')
|
|
101
|
+
end)
|
|
102
|
+
end
|
|
103
|
+
end.list.map(&:join)
|
|
104
|
+
end
|
|
103
105
|
end
|
|
104
106
|
end
|
|
@@ -108,22 +108,44 @@ describe 'Save to .json (JSON)' do
|
|
|
108
108
|
# NOTE: step 2) read saved file
|
|
109
109
|
file_data = File.read(config_file_path)
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
111
|
+
expected_json =
|
|
112
|
+
if RUBY_ENGINE.include?('truffleruby')
|
|
113
|
+
<<~JSON.strip
|
|
114
|
+
{
|
|
115
|
+
"true_bollean": true,
|
|
116
|
+
"false_boolean": false,
|
|
117
|
+
"empty_object": {},
|
|
118
|
+
"filled_object": {
|
|
119
|
+
"a": 1,
|
|
120
|
+
"b": null,
|
|
121
|
+
"c": true,
|
|
122
|
+
"d": "1",
|
|
123
|
+
"e": false
|
|
124
|
+
},
|
|
125
|
+
"null_data": null,
|
|
126
|
+
"collection": ["1",2,true,false,null,[],{}]
|
|
127
|
+
}
|
|
128
|
+
JSON
|
|
129
|
+
else
|
|
130
|
+
<<~JSON.strip
|
|
131
|
+
{
|
|
132
|
+
"true_bollean": true,
|
|
133
|
+
"false_boolean": false,
|
|
134
|
+
"empty_object": {},
|
|
135
|
+
"filled_object": {
|
|
136
|
+
"a": 1,
|
|
137
|
+
"b": null,
|
|
138
|
+
"c": true,
|
|
139
|
+
"d": "1",
|
|
140
|
+
"e": false
|
|
141
|
+
},
|
|
142
|
+
"null_data": null,
|
|
143
|
+
"collection": [ "1", 2, true, false, null, [], {}]
|
|
144
|
+
}
|
|
145
|
+
JSON
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
expect(file_data).to eq(expected_json)
|
|
127
149
|
end
|
|
128
150
|
end
|
|
129
151
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qonfig
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.31.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rustam Ibragimov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: base64
|
|
@@ -154,7 +153,6 @@ extra_rdoc_files: []
|
|
|
154
153
|
files:
|
|
155
154
|
- ".github/workflows/build.yml"
|
|
156
155
|
- ".gitignore"
|
|
157
|
-
- ".jrubyrc"
|
|
158
156
|
- ".rspec"
|
|
159
157
|
- ".rubocop.yml"
|
|
160
158
|
- CHANGELOG.md
|
|
@@ -319,6 +317,7 @@ files:
|
|
|
319
317
|
- spec/features/load_setting_values_from_file/load_from_yaml_spec.rb
|
|
320
318
|
- spec/features/load_setting_values_from_file/shared_behavior_spec.rb
|
|
321
319
|
- spec/features/mixin_spec.rb
|
|
320
|
+
- spec/features/nested_access_caching_spec.rb
|
|
322
321
|
- spec/features/non_redefineable_core_methods_spec.rb
|
|
323
322
|
- spec/features/plugins/pretty_print_spec.rb
|
|
324
323
|
- spec/features/plugins/toml/expose_self/format_option_toml_spec.rb
|
|
@@ -399,7 +398,6 @@ homepage: https://github.com/0exp/qonfig
|
|
|
399
398
|
licenses:
|
|
400
399
|
- MIT
|
|
401
400
|
metadata: {}
|
|
402
|
-
post_install_message:
|
|
403
401
|
rdoc_options: []
|
|
404
402
|
require_paths:
|
|
405
403
|
- lib
|
|
@@ -414,8 +412,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
414
412
|
- !ruby/object:Gem::Version
|
|
415
413
|
version: '0'
|
|
416
414
|
requirements: []
|
|
417
|
-
rubygems_version: 3.
|
|
418
|
-
signing_key:
|
|
415
|
+
rubygems_version: 3.6.9
|
|
419
416
|
specification_version: 4
|
|
420
417
|
summary: Config object
|
|
421
418
|
test_files: []
|
data/.jrubyrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
debug.fullTrace=true
|