anyway_config 2.0.2 → 2.1.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/CHANGELOG.md +215 -181
- data/README.md +70 -10
- data/lib/.rbnext/1995.next/anyway/config.rb +391 -0
- data/lib/.rbnext/1995.next/anyway/dynamic_config.rb +27 -0
- data/lib/.rbnext/1995.next/anyway/env.rb +56 -0
- data/lib/.rbnext/1995.next/anyway/loaders/base.rb +21 -0
- data/lib/.rbnext/1995.next/anyway/tracing.rb +181 -0
- data/lib/.rbnext/2.7/anyway/config.rb +46 -69
- data/lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb +30 -0
- data/lib/.rbnext/2.7/anyway/settings.rb +79 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +18 -26
- data/lib/.rbnext/3.0/anyway/config.rb +391 -0
- data/lib/.rbnext/3.0/anyway/loaders.rb +77 -0
- data/lib/.rbnext/3.0/anyway/loaders/base.rb +21 -0
- data/lib/.rbnext/3.0/anyway/tracing.rb +183 -0
- data/lib/anyway/config.rb +46 -69
- data/lib/anyway/dynamic_config.rb +1 -1
- data/lib/anyway/env.rb +1 -1
- data/lib/anyway/ext/deep_dup.rb +6 -0
- data/lib/anyway/ext/hash.rb +0 -12
- data/lib/anyway/loaders.rb +1 -3
- data/lib/anyway/loaders/base.rb +2 -4
- data/lib/anyway/loaders/yaml.rb +4 -4
- data/lib/anyway/rails/loaders/credentials.rb +2 -2
- data/lib/anyway/rails/loaders/secrets.rb +1 -1
- data/lib/anyway/rails/loaders/yaml.rb +11 -0
- data/lib/anyway/rails/settings.rb +6 -0
- data/lib/anyway/settings.rb +52 -2
- data/lib/anyway/testing.rb +1 -1
- data/lib/anyway/tracing.rb +17 -27
- data/lib/anyway/utils/deep_merge.rb +21 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +3 -1
- metadata +20 -8
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
# Adds ability to generate anonymous (class-less) config dynamicly
|
5
|
+
# (like Rails.application.config_for but using more data sources).
|
6
|
+
module DynamicConfig
|
7
|
+
module ClassMethods
|
8
|
+
# Load config as Hash by any name
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# my_config = Anyway::Config.for(:my_app)
|
13
|
+
# # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"]
|
14
|
+
#
|
15
|
+
def for(name, **options)
|
16
|
+
config = allocate
|
17
|
+
options[:env_prefix] ||= name.to_s.upcase
|
18
|
+
options[:config_path] ||= config.resolve_config_path(name, options[:env_prefix])
|
19
|
+
config.load_from_sources(new_empty_config, name: name, **options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
base.extend ClassMethods
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
# Parses environment variables and provides
|
5
|
+
# method-like access
|
6
|
+
class Env
|
7
|
+
using RubyNext
|
8
|
+
using Anyway::Ext::DeepDup
|
9
|
+
using Anyway::Ext::Hash
|
10
|
+
|
11
|
+
include Tracing
|
12
|
+
|
13
|
+
attr_reader :data, :traces, :type_cast
|
14
|
+
|
15
|
+
def initialize(type_cast: AutoCast)
|
16
|
+
@type_cast = type_cast
|
17
|
+
@data = {}
|
18
|
+
@traces = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear
|
22
|
+
data.clear
|
23
|
+
traces.clear
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch(prefix)
|
27
|
+
return data[prefix].deep_dup if data.key?(prefix)
|
28
|
+
|
29
|
+
Tracing.capture do
|
30
|
+
data[prefix] = parse_env(prefix)
|
31
|
+
end.then do |trace|
|
32
|
+
traces[prefix] = trace
|
33
|
+
end
|
34
|
+
|
35
|
+
data[prefix].deep_dup
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_with_trace(prefix)
|
39
|
+
[fetch(prefix), traces[prefix]]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def parse_env(prefix)
|
45
|
+
match_prefix = "#{prefix}_"
|
46
|
+
ENV.each_pair.with_object({}) do |(key, val), data|
|
47
|
+
next unless key.start_with?(match_prefix)
|
48
|
+
|
49
|
+
path = key.sub(/^#{prefix}_/, "").downcase
|
50
|
+
|
51
|
+
paths = path.split("__")
|
52
|
+
trace!(:env, *paths, key: key) { data.bury(type_cast.call(val), *paths) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
module Loaders
|
5
|
+
class Base
|
6
|
+
include Tracing
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def call(local: Anyway::Settings.use_local_files, **opts)
|
10
|
+
new(local: local).call(**opts)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(local:)
|
15
|
+
@local = local
|
16
|
+
end
|
17
|
+
|
18
|
+
def use_local?() = @local == true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
# Provides method to trace values association
|
5
|
+
module Tracing
|
6
|
+
using Anyway::Ext::DeepDup
|
7
|
+
|
8
|
+
using(Module.new do
|
9
|
+
refine Hash do
|
10
|
+
def inspect
|
11
|
+
"{#{map { |k, v| "#{k}: #{v.inspect}" }.join(", ")}}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
refine Thread::Backtrace::Location do
|
16
|
+
def path_lineno() = "#{path}:#{lineno}"
|
17
|
+
end
|
18
|
+
end)
|
19
|
+
|
20
|
+
class Trace
|
21
|
+
UNDEF = Object.new
|
22
|
+
|
23
|
+
attr_reader :type, :value, :source
|
24
|
+
|
25
|
+
def initialize(type = :trace, value = UNDEF, **source)
|
26
|
+
@type = type
|
27
|
+
@source = source
|
28
|
+
@value = value == UNDEF ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
29
|
+
end
|
30
|
+
|
31
|
+
def dig(...)
|
32
|
+
value.dig(...)
|
33
|
+
end
|
34
|
+
|
35
|
+
def record_value(val, *path, **opts)
|
36
|
+
key = path.pop
|
37
|
+
if val.is_a?(Hash)
|
38
|
+
Trace.new.tap { _1.merge_values(val, **opts) }
|
39
|
+
else
|
40
|
+
Trace.new(:value, val, **opts)
|
41
|
+
end => trace
|
42
|
+
|
43
|
+
target_trace = path.empty? ? self : value.dig(*path)
|
44
|
+
target_trace.value[key.to_s] = trace
|
45
|
+
|
46
|
+
val
|
47
|
+
end
|
48
|
+
|
49
|
+
def merge_values(hash, **opts)
|
50
|
+
return hash unless hash
|
51
|
+
|
52
|
+
hash.each do |key, val|
|
53
|
+
if val.is_a?(Hash)
|
54
|
+
value[key.to_s].merge_values(val, **opts)
|
55
|
+
else
|
56
|
+
value[key.to_s] = Trace.new(:value, val, **opts)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
hash
|
61
|
+
end
|
62
|
+
|
63
|
+
def merge!(another_trace)
|
64
|
+
raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
|
65
|
+
raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
|
66
|
+
|
67
|
+
another_trace.value.each do |key, sub_trace|
|
68
|
+
if sub_trace.trace?
|
69
|
+
value[key].merge! sub_trace
|
70
|
+
else
|
71
|
+
value[key] = sub_trace
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def keep_if(...)
|
77
|
+
raise ArgumentError, "You can only filter :trace type, and this is :#{type}" unless trace?
|
78
|
+
value.keep_if(...)
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear() = value.clear
|
82
|
+
|
83
|
+
def trace?() = type == :trace
|
84
|
+
|
85
|
+
def to_h
|
86
|
+
if trace?
|
87
|
+
value.transform_values(&:to_h).tap { _1.default_proc = nil }
|
88
|
+
else
|
89
|
+
{value: value, source: source}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def dup() = self.class.new(type, value.dup, **source)
|
94
|
+
|
95
|
+
def pretty_print(q)
|
96
|
+
if trace?
|
97
|
+
q.nest(2) do
|
98
|
+
q.breakable ""
|
99
|
+
q.seplist(value, nil, :each) do |k, v|
|
100
|
+
q.group do
|
101
|
+
q.text k
|
102
|
+
q.text " =>"
|
103
|
+
q.breakable " " unless v.trace?
|
104
|
+
q.pp v
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
else
|
109
|
+
q.pp value
|
110
|
+
q.group(0, " (", ")") do
|
111
|
+
q.seplist(source, lambda { q.breakable " " }, :each) do |k, v|
|
112
|
+
q.group do
|
113
|
+
q.text k.to_s
|
114
|
+
q.text "="
|
115
|
+
q.text v.to_s
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class << self
|
124
|
+
def capture
|
125
|
+
unless Settings.tracing_enabled
|
126
|
+
yield
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
trace = Trace.new
|
131
|
+
trace_stack.push trace
|
132
|
+
yield
|
133
|
+
trace_stack.last
|
134
|
+
ensure
|
135
|
+
trace_stack.pop
|
136
|
+
end
|
137
|
+
|
138
|
+
def trace_stack
|
139
|
+
(Thread.current[:__anyway__trace_stack__] ||= [])
|
140
|
+
end
|
141
|
+
|
142
|
+
def current_trace() = trace_stack.last
|
143
|
+
|
144
|
+
alias_method :tracing?, :current_trace
|
145
|
+
|
146
|
+
def source_stack
|
147
|
+
(Thread.current[:__anyway__trace_source_stack__] ||= [])
|
148
|
+
end
|
149
|
+
|
150
|
+
def current_trace_source
|
151
|
+
source_stack.last || accessor_source(caller_locations(2, 1).first)
|
152
|
+
end
|
153
|
+
|
154
|
+
def with_trace_source(src)
|
155
|
+
source_stack << src
|
156
|
+
yield
|
157
|
+
ensure
|
158
|
+
source_stack.pop
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def accessor_source(location)
|
164
|
+
{type: :accessor, called_from: location.path_lineno}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module_function
|
169
|
+
|
170
|
+
def trace!(type, *path, **opts)
|
171
|
+
return yield unless Tracing.tracing?
|
172
|
+
val = yield
|
173
|
+
if val.is_a?(Hash)
|
174
|
+
Tracing.current_trace.merge_values(val, type: type, **opts)
|
175
|
+
else
|
176
|
+
Tracing.current_trace.record_value(val, *path, type: type, **opts)
|
177
|
+
end
|
178
|
+
val
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -11,9 +11,7 @@ module Anyway # :nodoc:
|
|
11
11
|
|
12
12
|
using(Module.new do
|
13
13
|
refine Object do
|
14
|
-
def vm_object_id
|
15
|
-
(object_id << 1).to_s(16)
|
16
|
-
end
|
14
|
+
def vm_object_id() ; (object_id << 1).to_s(16); end
|
17
15
|
end
|
18
16
|
end)
|
19
17
|
|
@@ -21,7 +19,7 @@ module Anyway # :nodoc:
|
|
21
19
|
# Provides `attr_config` method to describe
|
22
20
|
# configuration parameters and set defaults
|
23
21
|
class Config
|
24
|
-
PARAM_NAME = /^[a-z_](
|
22
|
+
PARAM_NAME = /^[a-z_](\w+)?$/
|
25
23
|
|
26
24
|
# List of names that couldn't be used as config names
|
27
25
|
# (the class instance methods we use)
|
@@ -42,12 +40,14 @@ module Anyway # :nodoc:
|
|
42
40
|
raise_validation_error
|
43
41
|
reload
|
44
42
|
resolve_config_path
|
43
|
+
tap
|
45
44
|
to_h
|
46
45
|
to_source_trace
|
47
46
|
write_config_attr
|
48
47
|
].freeze
|
49
48
|
|
50
49
|
class Error < StandardError; end
|
50
|
+
|
51
51
|
class ValidationError < Error; end
|
52
52
|
|
53
53
|
include OptparseConfig
|
@@ -72,9 +72,7 @@ module Anyway # :nodoc:
|
|
72
72
|
@name = name
|
73
73
|
end
|
74
74
|
|
75
|
-
def apply_to(config)
|
76
|
-
config.send(name)
|
77
|
-
end
|
75
|
+
def apply_to(config) ; config.send(name); end
|
78
76
|
end
|
79
77
|
|
80
78
|
class << self
|
@@ -110,23 +108,21 @@ module Anyway # :nodoc:
|
|
110
108
|
def defaults
|
111
109
|
return @defaults if instance_variable_defined?(:@defaults)
|
112
110
|
|
113
|
-
@defaults =
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
111
|
+
@defaults = if superclass < Anyway::Config
|
112
|
+
superclass.defaults.deep_dup
|
113
|
+
else
|
114
|
+
new_empty_config
|
115
|
+
end
|
119
116
|
end
|
120
117
|
|
121
118
|
def config_attributes
|
122
119
|
return @config_attributes if instance_variable_defined?(:@config_attributes)
|
123
120
|
|
124
|
-
@config_attributes =
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
121
|
+
@config_attributes = if superclass < Anyway::Config
|
122
|
+
superclass.config_attributes.dup
|
123
|
+
else
|
124
|
+
[]
|
125
|
+
end
|
130
126
|
end
|
131
127
|
|
132
128
|
def required(*names)
|
@@ -140,18 +136,17 @@ module Anyway # :nodoc:
|
|
140
136
|
def required_attributes
|
141
137
|
return @required_attributes if instance_variable_defined?(:@required_attributes)
|
142
138
|
|
143
|
-
@required_attributes =
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
139
|
+
@required_attributes = if superclass < Anyway::Config
|
140
|
+
superclass.required_attributes.dup
|
141
|
+
else
|
142
|
+
[]
|
143
|
+
end
|
149
144
|
end
|
150
145
|
|
151
146
|
def on_load(*names, &block)
|
152
|
-
raise ArgumentError, "Either methods or block should be specified, not both" if
|
147
|
+
raise ArgumentError, "Either methods or block should be specified, not both" if block && !names.empty?
|
153
148
|
|
154
|
-
if
|
149
|
+
if block
|
155
150
|
load_callbacks << BlockCallback.new(block)
|
156
151
|
else
|
157
152
|
load_callbacks.push(*names.map { |_1| NamedCallback.new(_1) })
|
@@ -161,12 +156,11 @@ module Anyway # :nodoc:
|
|
161
156
|
def load_callbacks
|
162
157
|
return @load_callbacks if instance_variable_defined?(:@load_callbacks)
|
163
158
|
|
164
|
-
@load_callbacks =
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
end
|
159
|
+
@load_callbacks = if superclass <= Anyway::Config
|
160
|
+
superclass.load_callbacks.dup
|
161
|
+
else
|
162
|
+
[]
|
163
|
+
end
|
170
164
|
end
|
171
165
|
|
172
166
|
def config_name(val = nil)
|
@@ -186,26 +180,21 @@ module Anyway # :nodoc:
|
|
186
180
|
end
|
187
181
|
end
|
188
182
|
|
189
|
-
def explicit_config_name?
|
190
|
-
!explicit_config_name.nil?
|
191
|
-
end
|
183
|
+
def explicit_config_name?() ; !explicit_config_name.nil?; end
|
192
184
|
|
193
185
|
def env_prefix(val = nil)
|
194
186
|
return (@env_prefix = val.to_s.upcase) unless val.nil?
|
195
187
|
|
196
188
|
return @env_prefix if instance_variable_defined?(:@env_prefix)
|
197
189
|
|
198
|
-
@env_prefix =
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
190
|
+
@env_prefix = if superclass < Anyway::Config && superclass.explicit_config_name?
|
191
|
+
superclass.env_prefix
|
192
|
+
else
|
193
|
+
config_name.upcase
|
194
|
+
end
|
204
195
|
end
|
205
196
|
|
206
|
-
def new_empty_config
|
207
|
-
{}
|
208
|
-
end
|
197
|
+
def new_empty_config() ; {}; end
|
209
198
|
|
210
199
|
private
|
211
200
|
|
@@ -213,9 +202,8 @@ module Anyway # :nodoc:
|
|
213
202
|
names.each do |name|
|
214
203
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
215
204
|
def #{name}=(val)
|
216
|
-
__trace__&.record_value(val, \"#{name}\", Tracing.current_trace_source)
|
217
|
-
|
218
|
-
@#{name} = values[:#{name}] = val
|
205
|
+
__trace__&.record_value(val, \"#{name}\", **Tracing.current_trace_source)
|
206
|
+
values[:#{name}] = val
|
219
207
|
end
|
220
208
|
|
221
209
|
def #{name}
|
@@ -242,7 +230,7 @@ module Anyway # :nodoc:
|
|
242
230
|
# handle two cases:
|
243
231
|
# - SomeModule::Config => "some_module"
|
244
232
|
# - SomeConfig => "some"
|
245
|
-
unless name =~ /^(\w+)(
|
233
|
+
unless name =~ /^(\w+)(::)?Config$/
|
246
234
|
raise "Couldn't infer config name, please, specify it explicitly" \
|
247
235
|
"via `config_name :my_config`"
|
248
236
|
end
|
@@ -301,17 +289,14 @@ module Anyway # :nodoc:
|
|
301
289
|
trace = Tracing.capture do
|
302
290
|
Tracing.trace!(:defaults) { base_config }
|
303
291
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
env_prefix: env_prefix,
|
308
|
-
config_path: resolve_config_path(config_name, env_prefix)
|
309
|
-
)
|
292
|
+
config_path = resolve_config_path(config_name, env_prefix)
|
293
|
+
|
294
|
+
load_from_sources(base_config, name: config_name, env_prefix: env_prefix, config_path: config_path)
|
310
295
|
|
311
296
|
if overrides
|
312
297
|
Tracing.trace!(:load) { overrides }
|
313
298
|
|
314
|
-
|
299
|
+
Utils.deep_merge!(base_config, overrides)
|
315
300
|
end
|
316
301
|
end
|
317
302
|
|
@@ -334,18 +319,14 @@ module Anyway # :nodoc:
|
|
334
319
|
|
335
320
|
def load_from_sources(base_config, **options)
|
336
321
|
Anyway.loaders.each do |(_id, loader)|
|
337
|
-
|
322
|
+
Utils.deep_merge!(base_config, loader.call(**options))
|
338
323
|
end
|
339
324
|
base_config
|
340
325
|
end
|
341
326
|
|
342
|
-
def dig(*keys)
|
343
|
-
values.dig(*keys)
|
344
|
-
end
|
327
|
+
def dig(*keys) ; values.dig(*keys); end
|
345
328
|
|
346
|
-
def to_h
|
347
|
-
values.deep_dup.deep_freeze
|
348
|
-
end
|
329
|
+
def to_h() ; values.deep_dup.deep_freeze; end
|
349
330
|
|
350
331
|
def dup
|
351
332
|
self.class.allocate.tap do |new_config|
|
@@ -360,13 +341,9 @@ module Anyway # :nodoc:
|
|
360
341
|
Anyway.env.fetch(env_prefix).delete("conf") || Settings.default_config_path.call(name)
|
361
342
|
end
|
362
343
|
|
363
|
-
def deconstruct_keys(keys)
|
364
|
-
values.deconstruct_keys(keys)
|
365
|
-
end
|
344
|
+
def deconstruct_keys(keys) ; values.deconstruct_keys(keys); end
|
366
345
|
|
367
|
-
def to_source_trace
|
368
|
-
__trace__&.to_h
|
369
|
-
end
|
346
|
+
def to_source_trace() ; __trace__&.to_h; end
|
370
347
|
|
371
348
|
def inspect
|
372
349
|
"#<#{self.class}:0x#{vm_object_id.rjust(16, "0")} config_name=\"#{config_name}\" env_prefix=\"#{env_prefix}\" " \
|