anyway_config 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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_]([\w]+)?$/
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
- if superclass < Anyway::Config
115
- superclass.defaults.deep_dup
116
- else
117
- new_empty_config
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
- if superclass < Anyway::Config
126
- superclass.config_attributes.dup
127
- else
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
- if superclass < Anyway::Config
145
- superclass.required_attributes.dup
146
- else
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 block_given? && !names.empty?
147
+ raise ArgumentError, "Either methods or block should be specified, not both" if block && !names.empty?
153
148
 
154
- if block_given?
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
- if superclass <= Anyway::Config
166
- superclass.load_callbacks.dup
167
- else
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
- if superclass < Anyway::Config && superclass.explicit_config_name?
200
- superclass.env_prefix
201
- else
202
- config_name.upcase
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
- # DEPRECATED: instance variable set will be removed in 2.1
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+)(\:\:)?Config$/
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
- load_from_sources(
305
- base_config,
306
- name: config_name,
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
- base_config.deep_merge!(overrides)
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
- base_config.deep_merge!(loader.call(**options))
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}\" " \