anyway_config 2.5.4 → 2.6.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 +4 -0
- data/README.md +8 -2
- data/lib/.rbnext/2.7/anyway/auto_cast.rb +2 -2
- data/lib/.rbnext/2.7/anyway/config.rb +5 -5
- data/lib/.rbnext/2.7/anyway/tracing.rb +10 -10
- data/lib/.rbnext/2.7/anyway/type_casting.rb +1 -1
- data/lib/.rbnext/3.0/anyway/auto_cast.rb +2 -2
- data/lib/.rbnext/3.0/anyway/config.rb +5 -5
- data/lib/.rbnext/3.0/anyway/loaders/base.rb +2 -2
- data/lib/.rbnext/3.0/anyway/tracing.rb +10 -10
- data/lib/.rbnext/3.1/anyway/config.rb +5 -5
- data/lib/.rbnext/3.1/anyway/loaders/base.rb +2 -2
- data/lib/.rbnext/3.1/anyway/tracing.rb +10 -10
- data/lib/.rbnext/3.2/anyway/config.rb +473 -0
- data/lib/.rbnext/3.2/anyway/loaders/base.rb +21 -0
- data/lib/.rbnext/3.2/anyway/tracing.rb +188 -0
- data/lib/.rbnext/3.4/anyway/auto_cast.rb +53 -0
- data/lib/.rbnext/3.4/anyway/config.rb +473 -0
- data/lib/.rbnext/3.4/anyway/tracing.rb +188 -0
- data/lib/.rbnext/3.4/anyway/type_casting.rb +144 -0
- data/lib/anyway/auto_cast.rb +2 -2
- data/lib/anyway/config.rb +5 -5
- data/lib/anyway/loaders/base.rb +2 -2
- data/lib/anyway/tracing.rb +10 -10
- data/lib/anyway/type_casting.rb +1 -1
- data/lib/anyway/version.rb +1 -1
- data/lib/rails/commands/local_credentials/local_credentials_command.rb +24 -0
- metadata +17 -9
@@ -0,0 +1,473 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anyway/optparse_config"
|
4
|
+
require "anyway/dynamic_config"
|
5
|
+
|
6
|
+
module Anyway # :nodoc:
|
7
|
+
using RubyNext
|
8
|
+
using Anyway::Ext::DeepDup
|
9
|
+
using Anyway::Ext::DeepFreeze
|
10
|
+
using Anyway::Ext::Hash
|
11
|
+
using Anyway::Ext::FlattenNames
|
12
|
+
|
13
|
+
using(Module.new do
|
14
|
+
refine Object do
|
15
|
+
def vm_object_id() = (object_id << 1).to_s(16)
|
16
|
+
end
|
17
|
+
end)
|
18
|
+
|
19
|
+
# Base config class
|
20
|
+
# Provides `attr_config` method to describe
|
21
|
+
# configuration parameters and set defaults
|
22
|
+
class Config
|
23
|
+
PARAM_NAME = /^[a-z_](\w+)?$/
|
24
|
+
|
25
|
+
# List of names that couldn't be used as config names
|
26
|
+
# (the class instance methods we use)
|
27
|
+
RESERVED_NAMES = %i[
|
28
|
+
config_name
|
29
|
+
env_prefix
|
30
|
+
as_env
|
31
|
+
values
|
32
|
+
class
|
33
|
+
clear
|
34
|
+
deconstruct_keys
|
35
|
+
dig
|
36
|
+
dup
|
37
|
+
initialize
|
38
|
+
load
|
39
|
+
load_from_sources
|
40
|
+
option_parser
|
41
|
+
pretty_print
|
42
|
+
raise_validation_error
|
43
|
+
reload
|
44
|
+
resolve_config_path
|
45
|
+
tap
|
46
|
+
to_h
|
47
|
+
to_source_trace
|
48
|
+
write_config_attr
|
49
|
+
__type_caster__
|
50
|
+
].freeze
|
51
|
+
|
52
|
+
class Error < StandardError; end
|
53
|
+
|
54
|
+
class ValidationError < Error; end
|
55
|
+
|
56
|
+
include OptparseConfig
|
57
|
+
include DynamicConfig
|
58
|
+
|
59
|
+
class BlockCallback
|
60
|
+
attr_reader :block
|
61
|
+
|
62
|
+
def initialize(block)
|
63
|
+
@block = block
|
64
|
+
end
|
65
|
+
|
66
|
+
def apply_to(config)
|
67
|
+
config.instance_exec(&block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class NamedCallback
|
72
|
+
attr_reader :name
|
73
|
+
|
74
|
+
def initialize(name)
|
75
|
+
@name = name
|
76
|
+
end
|
77
|
+
|
78
|
+
def apply_to(config) = config.send(name)
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
def attr_config(*args, **hargs)
|
83
|
+
new_defaults = hargs.deep_dup
|
84
|
+
new_defaults.stringify_keys!
|
85
|
+
|
86
|
+
defaults.merge! new_defaults
|
87
|
+
|
88
|
+
new_keys = ((args + new_defaults.keys) - config_attributes)
|
89
|
+
|
90
|
+
validate_param_names! new_keys.map(&:to_s)
|
91
|
+
|
92
|
+
new_keys.map!(&:to_sym)
|
93
|
+
|
94
|
+
unless (reserved_names = (new_keys & RESERVED_NAMES)).empty?
|
95
|
+
raise ArgumentError, "Can not use the following reserved names as config attrubutes: " \
|
96
|
+
"#{reserved_names.sort.map(&:to_s).join(", ")}"
|
97
|
+
end
|
98
|
+
|
99
|
+
config_attributes.push(*new_keys)
|
100
|
+
|
101
|
+
define_config_accessor(*new_keys)
|
102
|
+
|
103
|
+
# Define predicate methods ("param?") for attributes
|
104
|
+
# having `true` or `false` as default values
|
105
|
+
new_defaults.each do |key, val|
|
106
|
+
next unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
107
|
+
alias_method :"#{key}?", :"#{key}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def defaults
|
112
|
+
return @defaults if instance_variable_defined?(:@defaults)
|
113
|
+
|
114
|
+
@defaults = if superclass < Anyway::Config
|
115
|
+
superclass.defaults.deep_dup
|
116
|
+
else
|
117
|
+
new_empty_config
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def config_attributes
|
122
|
+
return @config_attributes if instance_variable_defined?(:@config_attributes)
|
123
|
+
|
124
|
+
@config_attributes = if superclass < Anyway::Config
|
125
|
+
superclass.config_attributes.dup
|
126
|
+
else
|
127
|
+
[]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def required(*names, env: nil, **nested)
|
132
|
+
unknown_names = names + nested.keys - config_attributes
|
133
|
+
raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
|
134
|
+
|
135
|
+
return unless Settings.matching_env?(env)
|
136
|
+
|
137
|
+
required_attributes.push(*names)
|
138
|
+
required_attributes.push(*nested.flatten_names)
|
139
|
+
end
|
140
|
+
|
141
|
+
def required_attributes
|
142
|
+
return @required_attributes if instance_variable_defined?(:@required_attributes)
|
143
|
+
|
144
|
+
@required_attributes = if superclass < Anyway::Config
|
145
|
+
superclass.required_attributes.dup
|
146
|
+
else
|
147
|
+
[]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def on_load(*names, &block)
|
152
|
+
raise ArgumentError, "Either methods or block should be specified, not both" if block && !names.empty?
|
153
|
+
|
154
|
+
if block
|
155
|
+
load_callbacks << BlockCallback.new(block)
|
156
|
+
else
|
157
|
+
load_callbacks.push(*names.map { it = _1;NamedCallback.new(it) })
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def load_callbacks
|
162
|
+
return @load_callbacks if instance_variable_defined?(:@load_callbacks)
|
163
|
+
|
164
|
+
@load_callbacks = if superclass <= Anyway::Config
|
165
|
+
superclass.load_callbacks.dup
|
166
|
+
else
|
167
|
+
[]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def config_name(val = nil)
|
172
|
+
return (@explicit_config_name = val.to_s) unless val.nil?
|
173
|
+
|
174
|
+
return @config_name if instance_variable_defined?(:@config_name)
|
175
|
+
|
176
|
+
@config_name = explicit_config_name || build_config_name
|
177
|
+
end
|
178
|
+
|
179
|
+
def explicit_config_name
|
180
|
+
return @explicit_config_name if instance_variable_defined?(:@explicit_config_name)
|
181
|
+
|
182
|
+
@explicit_config_name =
|
183
|
+
if superclass.respond_to?(:explicit_config_name)
|
184
|
+
superclass.explicit_config_name
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def explicit_config_name?() = !explicit_config_name.nil?
|
189
|
+
|
190
|
+
def env_prefix(val = nil)
|
191
|
+
return (@env_prefix = val.to_s.upcase) unless val.nil?
|
192
|
+
|
193
|
+
return @env_prefix if instance_variable_defined?(:@env_prefix)
|
194
|
+
|
195
|
+
@env_prefix = if superclass < Anyway::Config && superclass.explicit_config_name?
|
196
|
+
superclass.env_prefix
|
197
|
+
else
|
198
|
+
config_name.upcase
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def loader_options(val = nil)
|
203
|
+
return (@loader_options = val) unless val.nil?
|
204
|
+
|
205
|
+
return @loader_options if instance_variable_defined?(:@loader_options)
|
206
|
+
|
207
|
+
@loader_options = if superclass < Anyway::Config
|
208
|
+
superclass.loader_options
|
209
|
+
else
|
210
|
+
{}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def new_empty_config() = {}
|
215
|
+
|
216
|
+
def coerce_types(mapping)
|
217
|
+
Utils.deep_merge!(coercion_mapping, mapping)
|
218
|
+
|
219
|
+
mapping.each do |key, val|
|
220
|
+
type = val.is_a?(::Hash) ? val[:type] : val
|
221
|
+
next if type != :boolean
|
222
|
+
|
223
|
+
alias_method :"#{key}?", :"#{key}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def coercion_mapping
|
228
|
+
return @coercion_mapping if instance_variable_defined?(:@coercion_mapping)
|
229
|
+
|
230
|
+
@coercion_mapping = if superclass < Anyway::Config
|
231
|
+
superclass.coercion_mapping.deep_dup
|
232
|
+
else
|
233
|
+
{}
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def type_caster(val = nil)
|
238
|
+
return @type_caster unless val.nil?
|
239
|
+
|
240
|
+
@type_caster ||=
|
241
|
+
if coercion_mapping.empty?
|
242
|
+
fallback_type_caster
|
243
|
+
else
|
244
|
+
::Anyway::TypeCaster.new(coercion_mapping, fallback: fallback_type_caster)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def fallback_type_caster(val = nil)
|
249
|
+
return (@fallback_type_caster = val) unless val.nil?
|
250
|
+
|
251
|
+
return @fallback_type_caster if instance_variable_defined?(:@fallback_type_caster)
|
252
|
+
|
253
|
+
@fallback_type_caster = if superclass < Anyway::Config
|
254
|
+
superclass.fallback_type_caster.deep_dup
|
255
|
+
else
|
256
|
+
::Anyway::AutoCast
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def disable_auto_cast!
|
261
|
+
@fallback_type_caster = ::Anyway::NoCast
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def define_config_accessor(*names)
|
267
|
+
names.each do |name|
|
268
|
+
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
269
|
+
def #{name}=(val)
|
270
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
271
|
+
values[:#{name}] = val
|
272
|
+
end
|
273
|
+
|
274
|
+
def #{name}
|
275
|
+
values[:#{name}]
|
276
|
+
end
|
277
|
+
RUBY
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def accessors_module
|
282
|
+
return @accessors_module if instance_variable_defined?(:@accessors_module)
|
283
|
+
|
284
|
+
@accessors_module = Module.new.tap do |mod|
|
285
|
+
include mod
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def build_config_name
|
290
|
+
unless name
|
291
|
+
raise "Please, specify config name explicitly for anonymous class " \
|
292
|
+
"via `config_name :my_config`"
|
293
|
+
end
|
294
|
+
|
295
|
+
# handle two cases:
|
296
|
+
# - SomeModule::Config => "some_module"
|
297
|
+
# - SomeConfig => "some"
|
298
|
+
unless name =~ /^(\w+)(::)?Config$/
|
299
|
+
raise "Couldn't infer config name, please, specify it explicitly " \
|
300
|
+
"via `config_name :my_config`"
|
301
|
+
end
|
302
|
+
|
303
|
+
# TODO(v3.0): Replace downcase with underscore
|
304
|
+
Regexp.last_match[1].tap(&:downcase!)
|
305
|
+
end
|
306
|
+
|
307
|
+
def validate_param_names!(names)
|
308
|
+
invalid_names = names.reject { |name| name =~ PARAM_NAME }
|
309
|
+
return if invalid_names.empty?
|
310
|
+
|
311
|
+
raise ArgumentError, "Invalid attr_config name: #{invalid_names.join(", ")}.\n" \
|
312
|
+
"Valid names must satisfy /#{PARAM_NAME.source}/."
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
on_load :validate_required_attributes!
|
317
|
+
|
318
|
+
attr_reader :config_name, :env_prefix
|
319
|
+
|
320
|
+
# Instantiate config instance.
|
321
|
+
#
|
322
|
+
# Example:
|
323
|
+
#
|
324
|
+
# my_config = Anyway::Config.new()
|
325
|
+
#
|
326
|
+
# # provide some values explicitly
|
327
|
+
# my_config = Anyway::Config.new({some: :value})
|
328
|
+
#
|
329
|
+
def initialize(overrides = nil)
|
330
|
+
@config_name = self.class.config_name
|
331
|
+
|
332
|
+
raise ArgumentError, "Config name is missing" unless @config_name
|
333
|
+
|
334
|
+
@env_prefix = self.class.env_prefix
|
335
|
+
@values = {}
|
336
|
+
|
337
|
+
load(overrides)
|
338
|
+
end
|
339
|
+
|
340
|
+
def reload(overrides = nil)
|
341
|
+
clear
|
342
|
+
load(overrides)
|
343
|
+
self
|
344
|
+
end
|
345
|
+
|
346
|
+
def clear
|
347
|
+
values.clear
|
348
|
+
@__trace__ = nil
|
349
|
+
self
|
350
|
+
end
|
351
|
+
|
352
|
+
def load(overrides = nil)
|
353
|
+
base_config = self.class.defaults.deep_dup
|
354
|
+
|
355
|
+
trace = Tracing.capture do
|
356
|
+
Tracing.trace!(:defaults) { base_config }
|
357
|
+
|
358
|
+
config_path = resolve_config_path(config_name, env_prefix)
|
359
|
+
|
360
|
+
load_from_sources(
|
361
|
+
base_config,
|
362
|
+
name: config_name,
|
363
|
+
env_prefix:,
|
364
|
+
config_path:,
|
365
|
+
**self.class.loader_options
|
366
|
+
)
|
367
|
+
|
368
|
+
if overrides
|
369
|
+
Tracing.trace!(:load) { overrides }
|
370
|
+
|
371
|
+
Utils.deep_merge!(base_config, overrides)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
base_config.each do |key, val|
|
376
|
+
write_config_attr(key.to_sym, val)
|
377
|
+
end
|
378
|
+
|
379
|
+
# Trace may contain unknown attributes
|
380
|
+
trace&.keep_if { |key| self.class.config_attributes.include?(key.to_sym) }
|
381
|
+
|
382
|
+
# Run on_load callbacks
|
383
|
+
self.class.load_callbacks.each { it = _1;it.apply_to(self) }
|
384
|
+
|
385
|
+
# Set trace after we write all the values to
|
386
|
+
# avoid changing the source to accessor
|
387
|
+
@__trace__ = trace
|
388
|
+
|
389
|
+
self
|
390
|
+
end
|
391
|
+
|
392
|
+
def load_from_sources(base_config, **__kwrest__)
|
393
|
+
Anyway.loaders.each do |(_id, loader)|
|
394
|
+
Utils.deep_merge!(base_config, loader.call(**__kwrest__))
|
395
|
+
end
|
396
|
+
base_config
|
397
|
+
end
|
398
|
+
|
399
|
+
def dig(*__rest__) = values.dig(*__rest__)
|
400
|
+
|
401
|
+
def to_h() = values.deep_dup.deep_freeze
|
402
|
+
|
403
|
+
def dup
|
404
|
+
self.class.allocate.tap do |new_config|
|
405
|
+
%i[config_name env_prefix __trace__].each do |ivar|
|
406
|
+
new_config.instance_variable_set(:"@#{ivar}", send(ivar).dup)
|
407
|
+
end
|
408
|
+
new_config.instance_variable_set(:@values, values.deep_dup)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def resolve_config_path(name, env_prefix)
|
413
|
+
Anyway.env.fetch(env_prefix).delete("conf") || Settings.default_config_path.call(name)
|
414
|
+
end
|
415
|
+
|
416
|
+
def deconstruct_keys(keys) = values.deconstruct_keys(keys)
|
417
|
+
|
418
|
+
def to_source_trace() = __trace__&.to_h
|
419
|
+
|
420
|
+
def inspect
|
421
|
+
"#<#{self.class}:0x#{vm_object_id.rjust(16, "0")} config_name=\"#{config_name}\" env_prefix=\"#{env_prefix}\" " \
|
422
|
+
"values=#{values.inspect}>"
|
423
|
+
end
|
424
|
+
|
425
|
+
def pretty_print(q)
|
426
|
+
q.object_group self do
|
427
|
+
q.nest(1) do
|
428
|
+
q.breakable
|
429
|
+
q.text "config_name=#{config_name.inspect}"
|
430
|
+
q.breakable
|
431
|
+
q.text "env_prefix=#{env_prefix.inspect}"
|
432
|
+
q.breakable
|
433
|
+
q.text "values:"
|
434
|
+
q.pp __trace__
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
def as_env
|
440
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
441
|
+
end
|
442
|
+
|
443
|
+
private
|
444
|
+
|
445
|
+
attr_reader :values, :__trace__
|
446
|
+
|
447
|
+
def validate_required_attributes!
|
448
|
+
self.class.required_attributes.select do |name|
|
449
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
450
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
451
|
+
end.then do |missing|
|
452
|
+
next if missing.empty?
|
453
|
+
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def write_config_attr(key, val)
|
458
|
+
key = key.to_sym
|
459
|
+
return unless self.class.config_attributes.include?(key)
|
460
|
+
|
461
|
+
val = __type_caster__.coerce(key, val)
|
462
|
+
public_send(:"#{key}=", val)
|
463
|
+
end
|
464
|
+
|
465
|
+
def raise_validation_error(msg)
|
466
|
+
raise ValidationError, msg
|
467
|
+
end
|
468
|
+
|
469
|
+
def __type_caster__
|
470
|
+
self.class.type_caster
|
471
|
+
end
|
472
|
+
end
|
473
|
+
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, **__kwrest__)
|
10
|
+
new(local:).call(**__kwrest__)
|
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,188 @@
|
|
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 Thread::Backtrace::Location do
|
10
|
+
def path_lineno() = "#{path}:#{lineno}"
|
11
|
+
end
|
12
|
+
end)
|
13
|
+
|
14
|
+
class Trace
|
15
|
+
UNDEF = Object.new
|
16
|
+
|
17
|
+
attr_reader :type, :value, :source
|
18
|
+
|
19
|
+
def initialize(type = :trace, value = UNDEF, **source)
|
20
|
+
@type = type
|
21
|
+
@source = source
|
22
|
+
@value = (value == UNDEF) ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
23
|
+
end
|
24
|
+
|
25
|
+
def dig(...)
|
26
|
+
value.dig(...)
|
27
|
+
end
|
28
|
+
|
29
|
+
def record_value(val, *path, **__kwrest__)
|
30
|
+
key = path.pop
|
31
|
+
trace = if val.is_a?(Hash)
|
32
|
+
Trace.new.tap { it = _1;it.merge_values(val, **__kwrest__) }
|
33
|
+
else
|
34
|
+
Trace.new(:value, val, **__kwrest__)
|
35
|
+
end
|
36
|
+
|
37
|
+
target_trace = path.empty? ? self : value.dig(*path)
|
38
|
+
target_trace.record_key(key.to_s, trace)
|
39
|
+
|
40
|
+
val
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge_values(hash, **__kwrest__)
|
44
|
+
return hash unless hash
|
45
|
+
|
46
|
+
hash.each do |key, val|
|
47
|
+
if val.is_a?(Hash)
|
48
|
+
value[key.to_s].merge_values(val, **__kwrest__)
|
49
|
+
else
|
50
|
+
value[key.to_s] = Trace.new(:value, val, **__kwrest__)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
hash
|
55
|
+
end
|
56
|
+
|
57
|
+
def record_key(key, key_trace)
|
58
|
+
@value = Hash.new { |h, k| h[k] = Trace.new(:trace) } unless value.is_a?(::Hash)
|
59
|
+
|
60
|
+
value[key] = key_trace
|
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 { it = _1;it.default_proc = nil }
|
88
|
+
else
|
89
|
+
{value:, 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
|
+
if v.trace?
|
104
|
+
q.text " { "
|
105
|
+
q.pp v
|
106
|
+
q.breakable " "
|
107
|
+
q.text "}"
|
108
|
+
else
|
109
|
+
q.breakable " "
|
110
|
+
q.pp v
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
q.pp value
|
117
|
+
q.group(0, " (", ")") do
|
118
|
+
q.seplist(source, lambda { q.breakable " " }, :each) do |k, v|
|
119
|
+
q.group do
|
120
|
+
q.text k.to_s
|
121
|
+
q.text "="
|
122
|
+
q.text v.to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class << self
|
131
|
+
def capture
|
132
|
+
unless Settings.tracing_enabled
|
133
|
+
yield
|
134
|
+
return
|
135
|
+
end
|
136
|
+
|
137
|
+
trace = Trace.new
|
138
|
+
trace_stack.push trace
|
139
|
+
yield
|
140
|
+
trace_stack.last
|
141
|
+
ensure
|
142
|
+
trace_stack.pop
|
143
|
+
end
|
144
|
+
|
145
|
+
def trace_stack
|
146
|
+
(Thread.current[:__anyway__trace_stack__] ||= [])
|
147
|
+
end
|
148
|
+
|
149
|
+
def current_trace() = trace_stack.last
|
150
|
+
|
151
|
+
alias_method :tracing?, :current_trace
|
152
|
+
|
153
|
+
def source_stack
|
154
|
+
(Thread.current[:__anyway__trace_source_stack__] ||= [])
|
155
|
+
end
|
156
|
+
|
157
|
+
def current_trace_source
|
158
|
+
source_stack.last || accessor_source(caller_locations(2, 1).first)
|
159
|
+
end
|
160
|
+
|
161
|
+
def with_trace_source(src)
|
162
|
+
source_stack << src
|
163
|
+
yield
|
164
|
+
ensure
|
165
|
+
source_stack.pop
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def accessor_source(location)
|
171
|
+
{type: :accessor, called_from: location.path_lineno}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
module_function
|
176
|
+
|
177
|
+
def trace!(type, *path, **__kwrest__)
|
178
|
+
return yield unless Tracing.tracing?
|
179
|
+
val = yield
|
180
|
+
if val.is_a?(Hash)
|
181
|
+
Tracing.current_trace.merge_values(val, type:, **__kwrest__)
|
182
|
+
elsif !path.empty?
|
183
|
+
Tracing.current_trace.record_value(val, *path, type:, **__kwrest__)
|
184
|
+
end
|
185
|
+
val
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|