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,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}\" " \