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.
@@ -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}\" " \