anyway_config 2.0.6 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +199 -181
- data/README.md +69 -9
- data/lib/.rbnext/1995.next/anyway/config.rb +391 -0
- data/lib/.rbnext/1995.next/anyway/dynamic_config.rb +27 -0
- data/lib/.rbnext/1995.next/anyway/env.rb +56 -0
- data/lib/.rbnext/1995.next/anyway/loaders/base.rb +21 -0
- data/lib/.rbnext/1995.next/anyway/tracing.rb +181 -0
- data/lib/.rbnext/2.7/anyway/config.rb +12 -14
- data/lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb +30 -0
- data/lib/.rbnext/2.7/anyway/settings.rb +79 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +8 -6
- data/lib/.rbnext/{2.8 → 3.0}/anyway/config.rb +12 -14
- data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders.rb +0 -0
- data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders/base.rb +0 -0
- data/lib/.rbnext/{2.8 → 3.0}/anyway/tracing.rb +8 -6
- data/lib/anyway/config.rb +26 -28
- data/lib/anyway/dynamic_config.rb +1 -1
- data/lib/anyway/env.rb +1 -1
- data/lib/anyway/ext/deep_dup.rb +6 -0
- data/lib/anyway/ext/hash.rb +0 -12
- data/lib/anyway/loaders/base.rb +1 -1
- data/lib/anyway/loaders/yaml.rb +3 -3
- data/lib/anyway/rails/loaders/credentials.rb +2 -2
- data/lib/anyway/rails/loaders/secrets.rb +1 -1
- data/lib/anyway/rails/loaders/yaml.rb +11 -0
- data/lib/anyway/rails/settings.rb +6 -0
- data/lib/anyway/settings.rb +52 -2
- data/lib/anyway/tracing.rb +4 -4
- data/lib/anyway/utils/deep_merge.rb +21 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +2 -0
- metadata +18 -10
@@ -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
|
@@ -19,7 +19,7 @@ module Anyway # :nodoc:
|
|
19
19
|
# Provides `attr_config` method to describe
|
20
20
|
# configuration parameters and set defaults
|
21
21
|
class Config
|
22
|
-
PARAM_NAME = /^[a-z_](
|
22
|
+
PARAM_NAME = /^[a-z_](\w+)?$/
|
23
23
|
|
24
24
|
# List of names that couldn't be used as config names
|
25
25
|
# (the class instance methods we use)
|
@@ -40,12 +40,14 @@ module Anyway # :nodoc:
|
|
40
40
|
raise_validation_error
|
41
41
|
reload
|
42
42
|
resolve_config_path
|
43
|
+
tap
|
43
44
|
to_h
|
44
45
|
to_source_trace
|
45
46
|
write_config_attr
|
46
47
|
].freeze
|
47
48
|
|
48
49
|
class Error < StandardError; end
|
50
|
+
|
49
51
|
class ValidationError < Error; end
|
50
52
|
|
51
53
|
include OptparseConfig
|
@@ -142,9 +144,9 @@ module Anyway # :nodoc:
|
|
142
144
|
end
|
143
145
|
|
144
146
|
def on_load(*names, &block)
|
145
|
-
raise ArgumentError, "Either methods or block should be specified, not both" if
|
147
|
+
raise ArgumentError, "Either methods or block should be specified, not both" if block && !names.empty?
|
146
148
|
|
147
|
-
if
|
149
|
+
if block
|
148
150
|
load_callbacks << BlockCallback.new(block)
|
149
151
|
else
|
150
152
|
load_callbacks.push(*names.map { |_1| NamedCallback.new(_1) })
|
@@ -201,8 +203,7 @@ module Anyway # :nodoc:
|
|
201
203
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
202
204
|
def #{name}=(val)
|
203
205
|
__trace__&.record_value(val, \"#{name}\", **Tracing.current_trace_source)
|
204
|
-
|
205
|
-
@#{name} = values[:#{name}] = val
|
206
|
+
values[:#{name}] = val
|
206
207
|
end
|
207
208
|
|
208
209
|
def #{name}
|
@@ -229,7 +230,7 @@ module Anyway # :nodoc:
|
|
229
230
|
# handle two cases:
|
230
231
|
# - SomeModule::Config => "some_module"
|
231
232
|
# - SomeConfig => "some"
|
232
|
-
unless name =~ /^(\w+)(
|
233
|
+
unless name =~ /^(\w+)(::)?Config$/
|
233
234
|
raise "Couldn't infer config name, please, specify it explicitly" \
|
234
235
|
"via `config_name :my_config`"
|
235
236
|
end
|
@@ -288,17 +289,14 @@ module Anyway # :nodoc:
|
|
288
289
|
trace = Tracing.capture do
|
289
290
|
Tracing.trace!(:defaults) { base_config }
|
290
291
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
env_prefix: env_prefix,
|
295
|
-
config_path: resolve_config_path(config_name, env_prefix)
|
296
|
-
)
|
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)
|
297
295
|
|
298
296
|
if overrides
|
299
297
|
Tracing.trace!(:load) { overrides }
|
300
298
|
|
301
|
-
|
299
|
+
Utils.deep_merge!(base_config, overrides)
|
302
300
|
end
|
303
301
|
end
|
304
302
|
|
@@ -321,7 +319,7 @@ module Anyway # :nodoc:
|
|
321
319
|
|
322
320
|
def load_from_sources(base_config, **options)
|
323
321
|
Anyway.loaders.each do |(_id, loader)|
|
324
|
-
|
322
|
+
Utils.deep_merge!(base_config, loader.call(**options))
|
325
323
|
end
|
326
324
|
base_config
|
327
325
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
module Rails
|
5
|
+
module Loaders
|
6
|
+
class YAML < Anyway::Loaders::YAML
|
7
|
+
def load_base_yml(*)
|
8
|
+
parsed_yml = super
|
9
|
+
return parsed_yml unless environmental?(parsed_yml)
|
10
|
+
|
11
|
+
super[::Rails.env] || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def environmental?(parsed_yml)
|
17
|
+
return true unless Settings.future.unwrap_known_environments
|
18
|
+
# likely
|
19
|
+
return true if parsed_yml.key?(::Rails.env)
|
20
|
+
# less likely
|
21
|
+
::Rails.application.config.anyway_config.known_environments.any? { |_1| parsed_yml.key?(_1) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def relative_config_path(path)
|
25
|
+
Pathname.new(path).relative_path_from(::Rails.root)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
# Use Settings name to not confuse with Config.
|
5
|
+
#
|
6
|
+
# Settings contain the library-wide configuration.
|
7
|
+
class Settings
|
8
|
+
# Future encapsulates settings that will be introduced in the upcoming version
|
9
|
+
# with the default values, which could break compatibility
|
10
|
+
class Future
|
11
|
+
class << self
|
12
|
+
def setting(name, default_value)
|
13
|
+
settings[name] = default_value
|
14
|
+
|
15
|
+
define_method(name) do
|
16
|
+
store[name]
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method(:"#{name}=") do |val|
|
20
|
+
store[name] = val
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def settings
|
25
|
+
@settings ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@store = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def use(*names)
|
34
|
+
store.clear
|
35
|
+
names.each { |_1| store[_1] = self.class.settings[_1] }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :store
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# Define whether to load data from
|
45
|
+
# *.yml.local (or credentials/local.yml.enc)
|
46
|
+
attr_accessor :use_local_files
|
47
|
+
|
48
|
+
# A proc returning a path to YML config file given the config name
|
49
|
+
attr_reader :default_config_path
|
50
|
+
|
51
|
+
def default_config_path=(val)
|
52
|
+
if val.is_a?(Proc)
|
53
|
+
@default_config_path = val
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
val = val.to_s
|
58
|
+
|
59
|
+
@default_config_path = ->(name) { File.join(val, "#{name}.yml") }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Enable source tracing
|
63
|
+
attr_accessor :tracing_enabled
|
64
|
+
|
65
|
+
def future
|
66
|
+
@future ||= Future.new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# By default, use local files only in development (that's the purpose if the local files)
|
71
|
+
self.use_local_files = (ENV["RACK_ENV"] == "development" || ENV["RAILS_ENV"] == "development")
|
72
|
+
|
73
|
+
# By default, consider configs are stored in the ./config folder
|
74
|
+
self.default_config_path = ->(name) { "./config/#{name}.yml" }
|
75
|
+
|
76
|
+
# Tracing is enabled by default
|
77
|
+
self.tracing_enabled = true
|
78
|
+
end
|
79
|
+
end
|