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,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ using RubyNext
5
+
6
+ module Loaders
7
+ class Registry
8
+ attr_reader :registry
9
+
10
+ def initialize
11
+ @registry = []
12
+ end
13
+
14
+ def prepend(id, handler = nil, &block)
15
+ handler ||= block
16
+ insert_at(0, id, handler)
17
+ end
18
+
19
+ def append(id, handler = nil, &block)
20
+ handler ||= block
21
+ insert_at(registry.size, id, handler)
22
+ end
23
+
24
+ def insert_before(another_id, id, handler = nil, &block)
25
+ ind = registry.find_index { |(hid, _)| hid == another_id }
26
+ raise ArgumentError, "Loader with ID #{another_id} hasn't been registered" if ind.nil?
27
+
28
+ handler ||= block
29
+ insert_at(ind, id, handler)
30
+ end
31
+
32
+ def insert_after(another_id, id, handler = nil, &block)
33
+ ind = registry.find_index { |(hid, _)| hid == another_id }
34
+ raise ArgumentError, "Loader with ID #{another_id} hasn't been registered" if ind.nil?
35
+
36
+ handler ||= block
37
+ insert_at(ind + 1, id, handler)
38
+ end
39
+
40
+ def override(id, handler)
41
+ find(id).then do |id_to_handler|
42
+ raise ArgumentError, "Loader with ID #{id} hasn't been registered" if id_to_handler.nil?
43
+ id_to_handler[1] = handler
44
+ end
45
+ end
46
+
47
+ def delete(id)
48
+ find(id).then do |id_to_handler|
49
+ raise ArgumentError, "Loader with ID #{id} hasn't been registered" if id_to_handler.nil?
50
+ registry.delete id_to_handler
51
+ end
52
+ end
53
+
54
+ def each(&block)
55
+ registry.each(&block)
56
+ end
57
+
58
+ def freeze() ; registry.freeze; end
59
+
60
+ private
61
+
62
+ def insert_at(index, id, handler)
63
+ raise ArgumentError, "Loader with ID #{id} has been already registered" unless find(id).nil?
64
+
65
+ registry.insert(index, [id, handler])
66
+ end
67
+
68
+ def find(id)
69
+ registry.find { |(hid, _)| hid == id }
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ require "anyway/loaders/base"
76
+ require "anyway/loaders/yaml"
77
+ require "anyway/loaders/env"
@@ -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; end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,183 @@
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}"; end
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
+ (__m__ = if val.is_a?(Hash)
38
+ Trace.new.tap {
39
+ _1.merge_values(val, **opts)
40
+ }
41
+ else
42
+ Trace.new(:value, val, **opts)
43
+ end) && (((trace = __m__) || true) || Kernel.raise(NoMatchingPatternError, __m__.inspect))
44
+
45
+ target_trace = path.empty? ? self : value.dig(*path)
46
+ target_trace.value[key.to_s] = trace
47
+
48
+ val
49
+ end
50
+
51
+ def merge_values(hash, **opts)
52
+ return hash unless hash
53
+
54
+ hash.each do |key, val|
55
+ if val.is_a?(Hash)
56
+ value[key.to_s].merge_values(val, **opts)
57
+ else
58
+ value[key.to_s] = Trace.new(:value, val, **opts)
59
+ end
60
+ end
61
+
62
+ hash
63
+ end
64
+
65
+ def merge!(another_trace)
66
+ raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
67
+ raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
68
+
69
+ another_trace.value.each do |key, sub_trace|
70
+ if sub_trace.trace?
71
+ value[key].merge! sub_trace
72
+ else
73
+ value[key] = sub_trace
74
+ end
75
+ end
76
+ end
77
+
78
+ def keep_if(...)
79
+ raise ArgumentError, "You can only filter :trace type, and this is :#{type}" unless trace?
80
+ value.keep_if(...)
81
+ end
82
+
83
+ def clear() ; value.clear; end
84
+
85
+ def trace?() ; type == :trace; end
86
+
87
+ def to_h
88
+ if trace?
89
+ value.transform_values(&:to_h).tap { _1.default_proc = nil }
90
+ else
91
+ {value: value, source: source}
92
+ end
93
+ end
94
+
95
+ def dup() ; self.class.new(type, value.dup, **source); end
96
+
97
+ def pretty_print(q)
98
+ if trace?
99
+ q.nest(2) do
100
+ q.breakable ""
101
+ q.seplist(value, nil, :each) do |k, v|
102
+ q.group do
103
+ q.text k
104
+ q.text " =>"
105
+ q.breakable " " unless v.trace?
106
+ q.pp v
107
+ end
108
+ end
109
+ end
110
+ else
111
+ q.pp value
112
+ q.group(0, " (", ")") do
113
+ q.seplist(source, lambda { q.breakable " " }, :each) do |k, v|
114
+ q.group do
115
+ q.text k.to_s
116
+ q.text "="
117
+ q.text v.to_s
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ class << self
126
+ def capture
127
+ unless Settings.tracing_enabled
128
+ yield
129
+ return
130
+ end
131
+
132
+ trace = Trace.new
133
+ trace_stack.push trace
134
+ yield
135
+ trace_stack.last
136
+ ensure
137
+ trace_stack.pop
138
+ end
139
+
140
+ def trace_stack
141
+ (Thread.current[:__anyway__trace_stack__] ||= [])
142
+ end
143
+
144
+ def current_trace() ; trace_stack.last; end
145
+
146
+ alias_method :tracing?, :current_trace
147
+
148
+ def source_stack
149
+ (Thread.current[:__anyway__trace_source_stack__] ||= [])
150
+ end
151
+
152
+ def current_trace_source
153
+ source_stack.last || accessor_source(caller_locations(2, 1).first)
154
+ end
155
+
156
+ def with_trace_source(src)
157
+ source_stack << src
158
+ yield
159
+ ensure
160
+ source_stack.pop
161
+ end
162
+
163
+ private
164
+
165
+ def accessor_source(location)
166
+ {type: :accessor, called_from: location.path_lineno}
167
+ end
168
+ end
169
+
170
+ module_function
171
+
172
+ def trace!(type, *path, **opts)
173
+ return yield unless Tracing.tracing?
174
+ val = yield
175
+ if val.is_a?(Hash)
176
+ Tracing.current_trace.merge_values(val, type: type, **opts)
177
+ else
178
+ Tracing.current_trace.record_value(val, *path, type: type, **opts)
179
+ end
180
+ val
181
+ end
182
+ end
183
+ 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)
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)
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 { 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?
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() = {}
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:, 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)
345
328
 
346
- def to_h
347
- values.deep_dup.deep_freeze
348
- end
329
+ def to_h() = values.deep_dup.deep_freeze
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)
366
345
 
367
- def to_source_trace
368
- __trace__&.to_h
369
- end
346
+ def to_source_trace() = __trace__&.to_h
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}\" " \