runger_config 3.0.1 → 5.0.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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -35
  3. data/README.md +83 -83
  4. data/lib/generators/runger/app_config/app_config_generator.rb +13 -0
  5. data/lib/generators/runger/config/config_generator.rb +49 -0
  6. data/lib/generators/runger/install/install_generator.rb +45 -0
  7. data/lib/generators/{anyway → runger}/install/templates/application_config.rb.tt +1 -1
  8. data/lib/{anyway → runger}/auto_cast.rb +4 -4
  9. data/lib/{anyway → runger}/config.rb +124 -104
  10. data/lib/runger/dynamic_config.rb +29 -0
  11. data/lib/runger/ejson_parser.rb +40 -0
  12. data/lib/runger/env.rb +70 -0
  13. data/lib/runger/ext/deep_dup.rb +45 -0
  14. data/lib/runger/ext/deep_freeze.rb +40 -0
  15. data/lib/runger/ext/flatten_names.rb +33 -0
  16. data/lib/runger/ext/hash.rb +37 -0
  17. data/lib/runger/ext/string_constantize.rb +21 -0
  18. data/lib/runger/loaders/base.rb +17 -0
  19. data/lib/runger/loaders/doppler.rb +57 -0
  20. data/lib/runger/loaders/ejson.rb +91 -0
  21. data/lib/runger/loaders/env.rb +12 -0
  22. data/lib/runger/loaders/yaml.rb +86 -0
  23. data/lib/runger/loaders.rb +75 -0
  24. data/lib/runger/option_parser_builder.rb +27 -0
  25. data/lib/{anyway → runger}/optparse_config.rb +15 -14
  26. data/lib/runger/rails/autoload.rb +38 -0
  27. data/lib/runger/rails/config.rb +19 -0
  28. data/lib/runger/rails/loaders/credentials.rb +58 -0
  29. data/lib/runger/rails/loaders/secrets.rb +31 -0
  30. data/lib/runger/rails/loaders/yaml.rb +4 -0
  31. data/lib/runger/rails/loaders.rb +5 -0
  32. data/lib/runger/rails/settings.rb +83 -0
  33. data/lib/runger/rails.rb +22 -0
  34. data/lib/{anyway → runger}/railtie.rb +9 -8
  35. data/lib/{anyway → runger}/rbs.rb +30 -30
  36. data/lib/runger/settings.rb +109 -0
  37. data/lib/runger/testing/helpers.rb +34 -0
  38. data/lib/{anyway → runger}/testing.rb +3 -3
  39. data/lib/runger/tracing.rb +195 -0
  40. data/lib/{anyway → runger}/type_casting.rb +19 -14
  41. data/lib/{anyway → runger}/utils/deep_merge.rb +2 -2
  42. data/lib/runger/utils/which.rb +16 -0
  43. data/lib/runger/version.rb +5 -0
  44. data/lib/{anyway.rb → runger.rb} +1 -1
  45. data/lib/runger_config.rb +56 -0
  46. data/sig/{anyway_config.rbs → runger_config.rbs} +1 -1
  47. metadata +68 -68
  48. data/lib/anyway/dynamic_config.rb +0 -31
  49. data/lib/anyway/ejson_parser.rb +0 -40
  50. data/lib/anyway/env.rb +0 -72
  51. data/lib/anyway/ext/deep_dup.rb +0 -48
  52. data/lib/anyway/ext/deep_freeze.rb +0 -44
  53. data/lib/anyway/ext/flatten_names.rb +0 -37
  54. data/lib/anyway/ext/hash.rb +0 -40
  55. data/lib/anyway/ext/string_constantize.rb +0 -24
  56. data/lib/anyway/loaders/base.rb +0 -21
  57. data/lib/anyway/loaders/doppler.rb +0 -61
  58. data/lib/anyway/loaders/ejson.rb +0 -89
  59. data/lib/anyway/loaders/env.rb +0 -16
  60. data/lib/anyway/loaders/yaml.rb +0 -83
  61. data/lib/anyway/loaders.rb +0 -77
  62. data/lib/anyway/option_parser_builder.rb +0 -29
  63. data/lib/anyway/rails/autoload.rb +0 -40
  64. data/lib/anyway/rails/config.rb +0 -23
  65. data/lib/anyway/rails/loaders/credentials.rb +0 -62
  66. data/lib/anyway/rails/loaders/secrets.rb +0 -35
  67. data/lib/anyway/rails/loaders/yaml.rb +0 -9
  68. data/lib/anyway/rails/loaders.rb +0 -5
  69. data/lib/anyway/rails/settings.rb +0 -83
  70. data/lib/anyway/rails.rb +0 -24
  71. data/lib/anyway/settings.rb +0 -111
  72. data/lib/anyway/testing/helpers.rb +0 -36
  73. data/lib/anyway/tracing.rb +0 -188
  74. data/lib/anyway/utils/which.rb +0 -18
  75. data/lib/anyway/version.rb +0 -5
  76. data/lib/anyway_config.rb +0 -49
  77. data/lib/generators/anyway/app_config/app_config_generator.rb +0 -17
  78. data/lib/generators/anyway/config/config_generator.rb +0 -46
  79. data/lib/generators/anyway/install/install_generator.rb +0 -47
  80. /data/lib/generators/{anyway → runger}/app_config/USAGE +0 -0
  81. /data/lib/generators/{anyway → runger}/config/USAGE +0 -0
  82. /data/lib/generators/{anyway → runger}/config/templates/config.rb.tt +0 -0
  83. /data/lib/generators/{anyway → runger}/config/templates/config.yml.tt +0 -0
  84. /data/lib/generators/{anyway → runger}/install/USAGE +0 -0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Base class for application config classes
4
- class ApplicationConfig < Anyway::Config
4
+ class ApplicationConfig < Runger::Config
5
5
  class << self
6
6
  # Make it possible to access a singleton config instance
7
7
  # via class methods (i.e., without explicitly calling `instance`)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Anyway
3
+ module Runger
4
4
  module AutoCast
5
5
  # Regexp to detect array values
6
6
  # Array value is a values that contains at least one comma
@@ -23,11 +23,11 @@ module Anyway
23
23
  when /\A(nil|null)\z/i
24
24
  nil
25
25
  when /\A\d+\z/
26
- val.to_i
26
+ Integer(val, 10)
27
27
  when /\A\d*\.\d+\z/
28
- val.to_f
28
+ Float(val)
29
29
  when /\A['"].*['"]\z/
30
- val.gsub(/(\A['"]|['"]\z)/, "")
30
+ val.gsub(/(\A['"]|['"]\z)/, '')
31
31
  else
32
32
  val
33
33
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/all"
4
- require "anyway/optparse_config"
5
- require "anyway/dynamic_config"
3
+ require 'active_support/all'
4
+ require 'runger/dynamic_config'
5
+ require 'runger/optparse_config'
6
6
 
7
- module Anyway # :nodoc:
8
- using Anyway::Ext::DeepDup
9
- using Anyway::Ext::DeepFreeze
10
- using Anyway::Ext::Hash
11
- using Anyway::Ext::FlattenNames
7
+ module Runger # :nodoc:
8
+ using Runger::Ext::DeepDup
9
+ using Runger::Ext::DeepFreeze
10
+ using Runger::Ext::Hash
11
+ using Runger::Ext::FlattenNames
12
12
 
13
13
  using(Module.new do
14
14
  refine Object do
@@ -20,6 +20,8 @@ module Anyway # :nodoc:
20
20
  # Provides `attr_config` method to describe
21
21
  # configuration parameters and set defaults
22
22
  class Config
23
+ include OptparseConfig
24
+ include DynamicConfig
23
25
  PARAM_NAME = /^[a-z_](\w+)?$/
24
26
 
25
27
  # List of names that couldn't be used as config names
@@ -53,9 +55,6 @@ module Anyway # :nodoc:
53
55
 
54
56
  class ValidationError < Error; end
55
57
 
56
- include OptparseConfig
57
- include DynamicConfig
58
-
59
58
  class BlockCallback
60
59
  attr_reader :block
61
60
 
@@ -83,17 +82,18 @@ module Anyway # :nodoc:
83
82
  new_defaults = hargs.deep_dup
84
83
  new_defaults.stringify_keys!
85
84
 
86
- defaults.merge! new_defaults
85
+ defaults.merge!(new_defaults)
87
86
 
88
87
  new_keys = ((args + new_defaults.keys) - config_attributes)
89
88
 
90
- validate_param_names! new_keys.map(&:to_s)
89
+ validate_param_names!(new_keys.map(&:to_s))
91
90
 
92
91
  new_keys.map!(&:to_sym)
93
92
 
94
93
  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(", ")}"
94
+ raise(ArgumentError,
95
+ 'Can not use the following reserved names as config attrubutes: ' \
96
+ "#{reserved_names.sort.map(&:to_s).join(', ')}")
97
97
  end
98
98
 
99
99
  config_attributes.push(*new_keys)
@@ -104,33 +104,39 @@ module Anyway # :nodoc:
104
104
  # having `true` or `false` as default values
105
105
  new_defaults.each do |key, val|
106
106
  next unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
107
- alias_method :"#{key}?", :"#{key}"
107
+
108
+ alias_method(:"#{key}?", :"#{key}")
108
109
  end
109
110
  end
110
111
 
111
112
  def defaults
112
113
  return @defaults if instance_variable_defined?(:@defaults)
113
114
 
114
- @defaults = if superclass < Anyway::Config
115
- superclass.defaults.deep_dup
116
- else
117
- new_empty_config
118
- end
115
+ @defaults =
116
+ if superclass < Runger::Config
117
+ superclass.defaults.deep_dup
118
+ else
119
+ new_empty_config
120
+ end
119
121
  end
120
122
 
121
123
  def config_attributes
122
124
  return @config_attributes if instance_variable_defined?(:@config_attributes)
123
125
 
124
- @config_attributes = if superclass < Anyway::Config
125
- superclass.config_attributes.dup
126
- else
127
- []
128
- end
126
+ @config_attributes =
127
+ if superclass < Runger::Config
128
+ superclass.config_attributes.dup
129
+ else
130
+ []
131
+ end
129
132
  end
130
133
 
131
134
  def required(*names, env: nil, **nested)
132
135
  unknown_names = names + nested.keys - config_attributes
133
- raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
136
+ if unknown_names.any?
137
+ raise(ArgumentError,
138
+ "Unknown config param: #{unknown_names.join(',')}")
139
+ end
134
140
 
135
141
  return unless Settings.matching_env?(env)
136
142
 
@@ -141,15 +147,19 @@ module Anyway # :nodoc:
141
147
  def required_attributes
142
148
  return @required_attributes if instance_variable_defined?(:@required_attributes)
143
149
 
144
- @required_attributes = if superclass < Anyway::Config
145
- superclass.required_attributes.dup
146
- else
147
- []
148
- end
150
+ @required_attributes =
151
+ if superclass < Runger::Config
152
+ superclass.required_attributes.dup
153
+ else
154
+ []
155
+ end
149
156
  end
150
157
 
151
158
  def on_load(*names, &block)
152
- raise ArgumentError, "Either methods or block should be specified, not both" if block && !names.empty?
159
+ if block && !names.empty?
160
+ raise(ArgumentError,
161
+ 'Either methods or block should be specified, not both')
162
+ end
153
163
 
154
164
  if block
155
165
  load_callbacks << BlockCallback.new(block)
@@ -161,11 +171,12 @@ module Anyway # :nodoc:
161
171
  def load_callbacks
162
172
  return @load_callbacks if instance_variable_defined?(:@load_callbacks)
163
173
 
164
- @load_callbacks = if superclass <= Anyway::Config
165
- superclass.load_callbacks.dup
166
- else
167
- []
168
- end
174
+ @load_callbacks =
175
+ if superclass <= Runger::Config
176
+ superclass.load_callbacks.dup
177
+ else
178
+ []
179
+ end
169
180
  end
170
181
 
171
182
  def config_name(val = nil)
@@ -192,11 +203,12 @@ module Anyway # :nodoc:
192
203
 
193
204
  return @env_prefix if instance_variable_defined?(:@env_prefix)
194
205
 
195
- @env_prefix = if superclass < Anyway::Config && superclass.explicit_config_name?
196
- superclass.env_prefix
197
- else
198
- config_name.upcase
199
- end
206
+ @env_prefix =
207
+ if superclass < Runger::Config && superclass.explicit_config_name?
208
+ superclass.env_prefix
209
+ else
210
+ config_name.upcase
211
+ end
200
212
  end
201
213
 
202
214
  def loader_options(val = nil)
@@ -204,34 +216,36 @@ module Anyway # :nodoc:
204
216
 
205
217
  return @loader_options if instance_variable_defined?(:@loader_options)
206
218
 
207
- @loader_options = if superclass < Anyway::Config
208
- superclass.loader_options
209
- else
210
- {}
211
- end
219
+ @loader_options =
220
+ if superclass < Runger::Config
221
+ superclass.loader_options
222
+ else
223
+ {}
224
+ end
212
225
  end
213
226
 
214
227
  def new_empty_config = {}
215
228
 
216
229
  def coerce_types(mapping)
217
- Utils.deep_merge!(coercion_mapping, mapping)
230
+ ::Runger::Utils.deep_merge!(coercion_mapping, mapping)
218
231
 
219
232
  mapping.each do |key, val|
220
233
  type = val.is_a?(::Hash) ? val[:type] : val
221
234
  next if type != :boolean
222
235
 
223
- alias_method :"#{key}?", :"#{key}"
236
+ alias_method(:"#{key}?", :"#{key}")
224
237
  end
225
238
  end
226
239
 
227
240
  def coercion_mapping
228
241
  return @coercion_mapping if instance_variable_defined?(:@coercion_mapping)
229
242
 
230
- @coercion_mapping = if superclass < Anyway::Config
231
- superclass.coercion_mapping.deep_dup
232
- else
233
- {}
234
- end
243
+ @coercion_mapping =
244
+ if superclass < Runger::Config
245
+ superclass.coercion_mapping.deep_dup
246
+ else
247
+ {}
248
+ end
235
249
  end
236
250
 
237
251
  def type_caster(val = nil)
@@ -241,7 +255,7 @@ module Anyway # :nodoc:
241
255
  if coercion_mapping.empty?
242
256
  fallback_type_caster
243
257
  else
244
- ::Anyway::TypeCaster.new(coercion_mapping, fallback: fallback_type_caster)
258
+ ::Runger::TypeCaster.new(coercion_mapping, fallback: fallback_type_caster)
245
259
  end
246
260
  end
247
261
 
@@ -250,22 +264,23 @@ module Anyway # :nodoc:
250
264
 
251
265
  return @fallback_type_caster if instance_variable_defined?(:@fallback_type_caster)
252
266
 
253
- @fallback_type_caster = if superclass < Anyway::Config
254
- superclass.fallback_type_caster.deep_dup
255
- else
256
- ::Anyway::AutoCast
257
- end
267
+ @fallback_type_caster =
268
+ if superclass < Runger::Config
269
+ superclass.fallback_type_caster.deep_dup
270
+ else
271
+ ::Runger::AutoCast
272
+ end
258
273
  end
259
274
 
260
275
  def disable_auto_cast!
261
- @fallback_type_caster = ::Anyway::NoCast
276
+ @fallback_type_caster = ::Runger::NoCast
262
277
  end
263
278
 
264
279
  private
265
280
 
266
281
  def define_config_accessor(*names)
267
282
  names.each do |name|
268
- accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
283
+ accessors_module.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
269
284
  def #{name}=(val)
270
285
  __trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
271
286
  values[:#{name}] = val
@@ -281,31 +296,33 @@ module Anyway # :nodoc:
281
296
  def accessors_module
282
297
  return @accessors_module if instance_variable_defined?(:@accessors_module)
283
298
 
284
- @accessors_module = Module.new.tap do |mod|
285
- include mod
286
- end
299
+ @accessors_module =
300
+ Module.new.tap do |mod|
301
+ include mod
302
+ end
287
303
  end
288
304
 
289
305
  def build_config_name
290
306
  unless name
291
- raise "Please, specify config name explicitly for anonymous class " \
292
- "via `config_name :my_config`"
307
+ raise('Please, specify config name explicitly for anonymous class ' \
308
+ 'via `config_name :my_config`')
293
309
  end
294
310
 
295
- unless name.underscore.gsub("/", "_") =~ /(\w+)_config\z/
296
- raise "Couldn't infer config name, please, specify it explicitly " \
297
- "via `config_name :my_config`"
311
+ unless name.underscore.tr('/', '_') =~ /(\w+)_config\z/
312
+ raise("Couldn't infer config name, please, specify it explicitly " \
313
+ 'via `config_name :my_config`')
298
314
  end
299
315
 
300
- Regexp.last_match[1].delete_suffix("_config").tap(&:downcase!)
316
+ Regexp.last_match[1].delete_suffix('_config').tap(&:downcase!)
301
317
  end
302
318
 
303
319
  def validate_param_names!(names)
304
- invalid_names = names.reject { |name| name =~ PARAM_NAME }
320
+ invalid_names = names.grep_v(PARAM_NAME)
305
321
  return if invalid_names.empty?
306
322
 
307
- raise ArgumentError, "Invalid attr_config name: #{invalid_names.join(", ")}.\n" \
308
- "Valid names must satisfy /#{PARAM_NAME.source}/."
323
+ raise(ArgumentError,
324
+ "Invalid attr_config name: #{invalid_names.join(', ')}.\n" \
325
+ "Valid names must satisfy /#{PARAM_NAME.source}/.")
309
326
  end
310
327
  end
311
328
 
@@ -317,15 +334,15 @@ module Anyway # :nodoc:
317
334
  #
318
335
  # Example:
319
336
  #
320
- # my_config = Anyway::Config.new()
337
+ # my_config = Runger::Config.new()
321
338
  #
322
339
  # # provide some values explicitly
323
- # my_config = Anyway::Config.new({some: :value})
340
+ # my_config = Runger::Config.new({some: :value})
324
341
  #
325
342
  def initialize(overrides = nil)
326
343
  @config_name = self.class.config_name
327
344
 
328
- raise ArgumentError, "Config name is missing" unless @config_name
345
+ raise(ArgumentError, 'Config name is missing') unless @config_name
329
346
 
330
347
  @env_prefix = self.class.env_prefix
331
348
  @values = {}
@@ -348,25 +365,26 @@ module Anyway # :nodoc:
348
365
  def load(overrides = nil)
349
366
  base_config = self.class.defaults.deep_dup
350
367
 
351
- trace = Tracing.capture do
352
- Tracing.trace!(:defaults) { base_config }
368
+ trace =
369
+ Tracing.capture do
370
+ Tracing.trace!(:defaults) { base_config }
353
371
 
354
- config_path = resolve_config_path(config_name, env_prefix)
372
+ config_path = resolve_config_path(config_name, env_prefix)
355
373
 
356
- load_from_sources(
357
- base_config,
358
- name: config_name,
359
- env_prefix:,
360
- config_path:,
361
- **self.class.loader_options
362
- )
374
+ load_from_sources(
375
+ base_config,
376
+ name: config_name,
377
+ env_prefix:,
378
+ config_path:,
379
+ **self.class.loader_options,
380
+ )
363
381
 
364
- if overrides
365
- Tracing.trace!(:load) { overrides }
382
+ if overrides
383
+ Tracing.trace!(:load) { overrides }
366
384
 
367
- Utils.deep_merge!(base_config, overrides)
385
+ ::Runger::Utils.deep_merge!(base_config, overrides)
386
+ end
368
387
  end
369
- end
370
388
 
371
389
  base_config.each do |key, val|
372
390
  write_config_attr(key.to_sym, val)
@@ -385,9 +403,9 @@ module Anyway # :nodoc:
385
403
  self
386
404
  end
387
405
 
388
- def load_from_sources(base_config, **)
389
- Anyway.loaders.each do |(_id, loader)|
390
- Utils.deep_merge!(base_config, loader.call(**))
406
+ def load_from_sources(base_config, **options)
407
+ Runger.loaders.each do |(_id, loader)|
408
+ ::Runger::Utils.deep_merge!(base_config, loader.call(**options))
391
409
  end
392
410
  base_config
393
411
  end
@@ -406,7 +424,7 @@ module Anyway # :nodoc:
406
424
  end
407
425
 
408
426
  def resolve_config_path(name, env_prefix)
409
- Anyway.env.fetch(env_prefix).delete("conf") || Settings.default_config_path.call(name)
427
+ Runger.env.fetch(env_prefix).delete('conf') || Settings.default_config_path.call(name)
410
428
  end
411
429
 
412
430
  def deconstruct_keys(keys) = values.deconstruct_keys(keys)
@@ -414,20 +432,21 @@ module Anyway # :nodoc:
414
432
  def to_source_trace = __trace__&.to_h
415
433
 
416
434
  def inspect
417
- "#<#{self.class}:0x#{vm_object_id.rjust(16, "0")} config_name=\"#{config_name}\" env_prefix=\"#{env_prefix}\" " \
418
- "values=#{values.inspect}>"
435
+ "#<#{self.class}:0x#{vm_object_id.rjust(16,
436
+ '0')} config_name=\"#{config_name}\" env_prefix=\"#{env_prefix}\" " \
437
+ "values=#{values.inspect}>"
419
438
  end
420
439
 
421
440
  def pretty_print(q)
422
- q.object_group self do
441
+ q.object_group(self) do
423
442
  q.nest(1) do
424
443
  q.breakable
425
444
  q.text "config_name=#{config_name.inspect}"
426
445
  q.breakable
427
446
  q.text "env_prefix=#{env_prefix.inspect}"
428
447
  q.breakable
429
- q.text "values:"
430
- q.pp __trace__
448
+ q.text 'values:'
449
+ q.pp(__trace__)
431
450
  end
432
451
  end
433
452
  end
@@ -442,11 +461,12 @@ module Anyway # :nodoc:
442
461
 
443
462
  def validate_required_attributes!
444
463
  self.class.required_attributes.select do |name|
445
- val = values.dig(*name.to_s.split(".").map(&:to_sym))
464
+ val = values.dig(*name.to_s.split('.').map(&:to_sym))
446
465
  val.nil? || (val.is_a?(String) && val.empty?)
447
466
  end.then do |missing|
448
467
  next if missing.empty?
449
- raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
468
+
469
+ raise_validation_error("The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(', ')}")
450
470
  end
451
471
  end
452
472
 
@@ -459,7 +479,7 @@ module Anyway # :nodoc:
459
479
  end
460
480
 
461
481
  def raise_validation_error(msg)
462
- raise ValidationError, msg
482
+ raise(ValidationError, msg)
463
483
  end
464
484
 
465
485
  def __type_caster__
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adds ability to generate anonymous (class-less) config dynamicly
4
+ # (like Rails.application.config_for but using more data sources).
5
+ module Runger::DynamicConfig
6
+ module ClassMethods
7
+ # Load config as Hash by any name
8
+ #
9
+ # Example:
10
+ #
11
+ # my_config = Runger::Config.for(:my_app)
12
+ # # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"]
13
+ #
14
+ def for(name, auto_cast: true, **options)
15
+ config = allocate
16
+ options[:env_prefix] ||= name.to_s.upcase
17
+ options[:config_path] ||= config.resolve_config_path(name, options[:env_prefix])
18
+
19
+ raw_config = config.load_from_sources(new_empty_config, name:, **options)
20
+ return raw_config unless auto_cast
21
+
22
+ ::Runger::AutoCast.call(raw_config)
23
+ end
24
+ end
25
+
26
+ def self.included(base)
27
+ base.extend(ClassMethods)
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'runger/ext/hash'
5
+
6
+ using Runger::Ext::Hash
7
+
8
+ class Runger::EJSONParser
9
+ attr_reader :bin_path
10
+
11
+ def initialize(bin_path = 'ejson')
12
+ @bin_path = bin_path
13
+ end
14
+
15
+ def call(file_path)
16
+ return unless File.exist?(file_path)
17
+
18
+ raw_content = nil
19
+
20
+ stdout, stderr, status = Open3.capture3("#{bin_path} decrypt #{file_path}")
21
+
22
+ if status.success?
23
+ raw_content = JSON.parse(stdout.chomp)
24
+ else
25
+ Kernel.warn("Failed to decrypt #{file_path}: #{stderr}")
26
+ end
27
+
28
+ return unless raw_content
29
+
30
+ raw_content.deep_transform_keys do |key|
31
+ if key[0] == '_'
32
+ # rubocop:disable Performance/ArraySemiInfiniteRangeSlice
33
+ key[1..]
34
+ # rubocop:enable Performance/ArraySemiInfiniteRangeSlice
35
+ else
36
+ key
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/runger/env.rb ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Parses environment variables and provides
4
+ # method-like access
5
+ class Runger::Env
6
+ using Runger::Ext::DeepDup
7
+ using Runger::Ext::Hash
8
+
9
+ class << self
10
+ def from_hash(hash, prefix: nil, memo: {})
11
+ hash.each do |key, value|
12
+ prefix_with_key = (prefix && !prefix.empty?) ? "#{prefix}_#{key.to_s.upcase}" : key.to_s.upcase
13
+
14
+ if value.is_a?(Hash)
15
+ from_hash(value, prefix: "#{prefix_with_key}_", memo:)
16
+ else
17
+ memo[prefix_with_key] = value.to_s
18
+ end
19
+ end
20
+
21
+ memo
22
+ end
23
+ end
24
+
25
+ include ::Runger::Tracing
26
+
27
+ attr_reader :data, :traces, :type_cast, :env_container
28
+
29
+ def initialize(type_cast: ::Runger::AutoCast, env_container: ENV)
30
+ @type_cast = type_cast
31
+ @data = {}
32
+ @traces = {}
33
+ @env_container = env_container
34
+ end
35
+
36
+ def clear
37
+ data.clear
38
+ traces.clear
39
+ end
40
+
41
+ def fetch(prefix)
42
+ return data[prefix].deep_dup if data.key?(prefix)
43
+
44
+ ::Runger::Tracing.capture do
45
+ data[prefix] = parse_env(prefix)
46
+ end.then do |trace|
47
+ traces[prefix] = trace
48
+ end
49
+
50
+ data[prefix].deep_dup
51
+ end
52
+
53
+ def fetch_with_trace(prefix)
54
+ [fetch(prefix), traces[prefix]]
55
+ end
56
+
57
+ private
58
+
59
+ def parse_env(prefix)
60
+ match_prefix = prefix.empty? ? prefix : "#{prefix}_"
61
+ env_container.each_pair.with_object({}) do |(key, val), data|
62
+ next unless key.start_with?(match_prefix)
63
+
64
+ path = key.sub(/^#{match_prefix}/, '').downcase
65
+
66
+ paths = path.split('__')
67
+ trace!(:env, *paths, key:) { data.bury(type_cast.call(val), *paths) }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extend Object through refinements
4
+ module Runger::Ext::DeepDup
5
+ refine ::Hash do
6
+ # Based on ActiveSupport http://api.rubyonrails.org/classes/Hash.html#method-i-deep_dup
7
+ def deep_dup
8
+ each_with_object(dup) do |(key, value), hash|
9
+ hash[key] =
10
+ if value.is_a?(::Hash) || value.is_a?(::Array)
11
+ value.deep_dup
12
+ else
13
+ value
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ refine ::Array do
20
+ # From ActiveSupport http://api.rubyonrails.org/classes/Array.html#method-i-deep_dup
21
+ def deep_dup
22
+ map do |value|
23
+ if value.is_a?(::Hash) || value.is_a?(::Array)
24
+ value.deep_dup
25
+ else
26
+ value
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ refine ::Object do
33
+ def deep_dup
34
+ dup
35
+ end
36
+ end
37
+
38
+ refine ::Module do
39
+ def deep_dup
40
+ self
41
+ end
42
+ end
43
+
44
+ using self
45
+ end