anyway_config 2.0.0.pre2 → 2.0.0.rc1
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 +350 -0
- data/LICENSE.txt +1 -1
- data/README.md +444 -119
- data/lib/.rbnext/2.7/anyway/auto_cast.rb +33 -0
- data/lib/.rbnext/2.7/anyway/config.rb +404 -0
- data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +31 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +187 -0
- data/lib/anyway/auto_cast.rb +33 -0
- data/lib/anyway/config.rb +235 -58
- data/lib/anyway/dynamic_config.rb +1 -1
- data/lib/anyway/env.rb +29 -19
- data/lib/anyway/ext/hash.rb +14 -6
- data/lib/anyway/loaders.rb +79 -0
- data/lib/anyway/loaders/base.rb +23 -0
- data/lib/anyway/loaders/env.rb +16 -0
- data/lib/anyway/loaders/yaml.rb +46 -0
- data/lib/anyway/option_parser_builder.rb +6 -3
- data/lib/anyway/optparse_config.rb +10 -6
- data/lib/anyway/rails.rb +16 -0
- data/lib/anyway/rails/config.rb +2 -69
- data/lib/anyway/rails/loaders.rb +5 -0
- data/lib/anyway/rails/loaders/credentials.rb +64 -0
- data/lib/anyway/rails/loaders/secrets.rb +39 -0
- data/lib/anyway/rails/loaders/yaml.rb +19 -0
- data/lib/anyway/rails/settings.rb +58 -0
- data/lib/anyway/railtie.rb +14 -2
- data/lib/anyway/settings.rb +29 -0
- data/lib/anyway/tracing.rb +187 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +27 -21
- data/lib/generators/anyway/app_config/USAGE +9 -0
- data/lib/generators/anyway/app_config/app_config_generator.rb +17 -0
- data/lib/generators/anyway/config/USAGE +13 -0
- data/lib/generators/anyway/config/config_generator.rb +46 -0
- data/lib/generators/anyway/config/templates/config.rb.tt +9 -0
- data/lib/generators/anyway/config/templates/config.yml.tt +13 -0
- data/lib/generators/anyway/install/USAGE +4 -0
- data/lib/generators/anyway/install/install_generator.rb +43 -0
- data/lib/generators/anyway/install/templates/application_config.rb.tt +17 -0
- metadata +75 -10
- data/lib/anyway/ext/string_serialize.rb +0 -38
- data/lib/anyway/loaders/env_loader.rb +0 -0
- data/lib/anyway/loaders/secrets_loader.rb +0 -0
- data/lib/anyway/loaders/yaml_loader.rb +0 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
using RubyNext
|
5
|
+
|
6
|
+
module Rails
|
7
|
+
module Loaders
|
8
|
+
class Credentials < Anyway::Loaders::Base
|
9
|
+
LOCAL_CONTENT_PATH = "config/credentials/local.yml.enc"
|
10
|
+
|
11
|
+
def call(name:, **_options)
|
12
|
+
return {} unless ::Rails.application.respond_to?(:credentials)
|
13
|
+
|
14
|
+
# do not load from credentials if we're in the context
|
15
|
+
# of the `credentials:edit` command
|
16
|
+
return {} if defined?(::Rails::Command::CredentialsCommand)
|
17
|
+
|
18
|
+
# Create a new hash cause credentials are mutable!
|
19
|
+
config = {}
|
20
|
+
|
21
|
+
trace!(
|
22
|
+
:credentials,
|
23
|
+
store: credentials_path
|
24
|
+
) do
|
25
|
+
::Rails.application.credentials.public_send(name)
|
26
|
+
end.then do |creds|
|
27
|
+
config.deep_merge!(creds) if creds
|
28
|
+
end
|
29
|
+
|
30
|
+
if use_local?
|
31
|
+
trace!(:credentials, store: LOCAL_CONTENT_PATH) do
|
32
|
+
local_credentials(name)
|
33
|
+
end.then { |creds| config.deep_merge!(creds) if creds }
|
34
|
+
end
|
35
|
+
|
36
|
+
config
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def local_credentials(name)
|
42
|
+
local_creds_path = ::Rails.root.join(LOCAL_CONTENT_PATH).to_s
|
43
|
+
|
44
|
+
return unless File.file?(local_creds_path)
|
45
|
+
|
46
|
+
creds = ::Rails.application.encrypted(
|
47
|
+
local_creds_path,
|
48
|
+
key_path: ::Rails.root.join("config/credentials/local.key")
|
49
|
+
)
|
50
|
+
|
51
|
+
creds.public_send(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def credentials_path
|
55
|
+
if ::Rails.application.config.respond_to?(:credentials)
|
56
|
+
::Rails.application.config.credentials.content_path.relative_path_from(::Rails.root).to_s
|
57
|
+
else
|
58
|
+
"config/credentials.yml.enc"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
using RubyNext
|
5
|
+
|
6
|
+
module Rails
|
7
|
+
module Loaders
|
8
|
+
class Secrets < Anyway::Loaders::Base
|
9
|
+
def call(name:, **_options)
|
10
|
+
return {} unless ::Rails.application.respond_to?(:secrets)
|
11
|
+
|
12
|
+
# Create a new hash cause secrets are mutable!
|
13
|
+
config = {}
|
14
|
+
|
15
|
+
trace!(:secrets) do
|
16
|
+
secrets.public_send(name)
|
17
|
+
end.then do |secrets|
|
18
|
+
config.deep_merge!(secrets) if secrets
|
19
|
+
end
|
20
|
+
|
21
|
+
config
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def secrets
|
27
|
+
@secrets ||= begin
|
28
|
+
::Rails.application.secrets.tap do |_|
|
29
|
+
# Reset secrets state if the app hasn't been initialized
|
30
|
+
# See https://github.com/palkan/anyway_config/issues/14
|
31
|
+
next if ::Rails.application.initialized?
|
32
|
+
::Rails.application.remove_instance_variable(:@secrets)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
module Rails
|
5
|
+
module Loaders
|
6
|
+
class YAML < Anyway::Loaders::YAML
|
7
|
+
def parse_yml(*)
|
8
|
+
super[::Rails.env] || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def relative_config_path(path)
|
14
|
+
Pathname.new(path).relative_path_from(::Rails.root)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Try to require zeitwerk
|
4
|
+
begin
|
5
|
+
require "zeitwerk"
|
6
|
+
require "active_support/dependencies/zeitwerk_integration"
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
10
|
+
module Anyway
|
11
|
+
class Settings
|
12
|
+
class << self
|
13
|
+
attr_reader :autoload_static_config_path
|
14
|
+
|
15
|
+
if defined?(::Zeitwerk)
|
16
|
+
attr_reader :autoloader
|
17
|
+
|
18
|
+
def autoload_static_config_path=(val)
|
19
|
+
raise "Cannot setup autoloader after application has been initialized" if ::Rails.application.initialized?
|
20
|
+
|
21
|
+
return unless ::Rails.root.join(val).exist?
|
22
|
+
|
23
|
+
autoloader&.unload
|
24
|
+
|
25
|
+
@autoload_static_config_path = val
|
26
|
+
|
27
|
+
# See https://github.com/rails/rails/blob/8ab4fd12f18203b83d0f252db96d10731485ff6a/railties/lib/rails/autoloaders.rb#L10
|
28
|
+
@autoloader = Zeitwerk::Loader.new.tap do |loader|
|
29
|
+
loader.tag = "anyway.config"
|
30
|
+
loader.inflector = ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector
|
31
|
+
loader.push_dir(::Rails.root.join(val))
|
32
|
+
loader.setup
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def cleanup_autoload_paths
|
37
|
+
return unless autoload_static_config_path
|
38
|
+
ActiveSupport::Dependencies.autoload_paths.delete(::Rails.root.join(autoload_static_config_path).to_s)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def autoload_static_config_path=(val)
|
42
|
+
if autoload_static_config_path
|
43
|
+
ActiveSupport::Dependencies.autoload_paths.delete(::Rails.root.join(autoload_static_config_path).to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
@autoload_static_config_path = val
|
47
|
+
ActiveSupport::Dependencies.autoload_paths << ::Rails.root.join(val)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup_autoload_paths
|
51
|
+
:no_op
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
self.default_config_path = ->(name) { ::Rails.root.join("config", "#{name}.yml") }
|
57
|
+
end
|
58
|
+
end
|
data/lib/anyway/railtie.rb
CHANGED
@@ -1,11 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Anyway # :nodoc:
|
4
|
+
DEFAULT_CONFIGS_PATH = "config/configs"
|
5
|
+
|
4
6
|
class Railtie < ::Rails::Railtie # :nodoc:
|
5
7
|
# Add settings to Rails config
|
6
8
|
config.anyway_config = Anyway::Settings
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
ActiveSupport.on_load(:before_configuration) do
|
11
|
+
config.anyway_config.autoload_static_config_path = DEFAULT_CONFIGS_PATH
|
12
|
+
end
|
13
|
+
|
14
|
+
# Remove `autoload_static_config_path` from Rails `autoload_paths`
|
15
|
+
# since we use our own autoloading mechanism
|
16
|
+
initializer "anyway_config.cleanup_autoload" do
|
17
|
+
Anyway::Settings.cleanup_autoload_paths
|
18
|
+
end
|
19
|
+
|
20
|
+
# Make sure loaders are not changed in runtime
|
21
|
+
config.after_initialize { Anyway.loaders.freeze }
|
10
22
|
end
|
11
23
|
end
|
@@ -0,0 +1,29 @@
|
|
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
|
+
class << self
|
9
|
+
# Define whether to load data from
|
10
|
+
# *.yml.local (or credentials/local.yml.enc)
|
11
|
+
attr_accessor :use_local_files
|
12
|
+
|
13
|
+
# Return a path to YML config file given the config name
|
14
|
+
attr_accessor :default_config_path
|
15
|
+
|
16
|
+
# Enable source tracing
|
17
|
+
attr_accessor :tracing_enabled
|
18
|
+
end
|
19
|
+
|
20
|
+
# By default, use local files only in development (that's the purpose if the local files)
|
21
|
+
self.use_local_files = (ENV["RACK_ENV"] == "development" || ENV["RAILS_ENV"] == "development")
|
22
|
+
|
23
|
+
# By default, consider configs are stored in the ./config folder
|
24
|
+
self.default_config_path = ->(name) { "./config/#{name}.yml" }
|
25
|
+
|
26
|
+
# Tracing is enabled by default
|
27
|
+
self.tracing_enabled = true
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,187 @@
|
|
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
|
17
|
+
"#{path}:#{lineno}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end)
|
21
|
+
|
22
|
+
class Trace
|
23
|
+
UNDEF = Object.new
|
24
|
+
|
25
|
+
attr_reader :type, :value, :source
|
26
|
+
|
27
|
+
def initialize(type = :trace, value = UNDEF, **source)
|
28
|
+
@type = type
|
29
|
+
@source = source
|
30
|
+
@value = value == UNDEF ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
31
|
+
end
|
32
|
+
|
33
|
+
def dig(...)
|
34
|
+
value.dig(...)
|
35
|
+
end
|
36
|
+
|
37
|
+
def record_value(val, *path, key, **opts)
|
38
|
+
trace =
|
39
|
+
if val.is_a?(Hash)
|
40
|
+
Trace.new.tap { _1.merge_values(val, **opts) }
|
41
|
+
else
|
42
|
+
Trace.new(:value, val, **opts)
|
43
|
+
end
|
44
|
+
target_trace = path.empty? ? self : value.dig(*path)
|
45
|
+
target_trace.value[key.to_s] = trace
|
46
|
+
|
47
|
+
val
|
48
|
+
end
|
49
|
+
|
50
|
+
def merge_values(hash, **opts)
|
51
|
+
return hash unless hash
|
52
|
+
|
53
|
+
hash.each do |key, val|
|
54
|
+
if val.is_a?(Hash)
|
55
|
+
value[key.to_s].merge_values(val, **opts)
|
56
|
+
else
|
57
|
+
value[key.to_s] = Trace.new(:value, val, **opts)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge!(another_trace)
|
65
|
+
raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
|
66
|
+
raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
|
67
|
+
|
68
|
+
another_trace.value.each do |key, sub_trace|
|
69
|
+
if sub_trace.trace?
|
70
|
+
value[key].merge! sub_trace
|
71
|
+
else
|
72
|
+
value[key] = sub_trace
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def keep_if(...)
|
78
|
+
raise ArgumentError, "You can only filter :trace type, and this is :#{type}" unless trace?
|
79
|
+
value.keep_if(...)
|
80
|
+
end
|
81
|
+
|
82
|
+
def clear
|
83
|
+
value.clear
|
84
|
+
end
|
85
|
+
|
86
|
+
def trace?
|
87
|
+
type == :trace
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_h
|
91
|
+
if trace?
|
92
|
+
value.transform_values(&:to_h).tap { _1.default_proc = nil }
|
93
|
+
else
|
94
|
+
{value: value, source: source}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def pretty_print(q)
|
99
|
+
if trace?
|
100
|
+
q.nest(2) do
|
101
|
+
q.breakable ""
|
102
|
+
q.seplist(value, nil, :each) do |k, v|
|
103
|
+
q.group do
|
104
|
+
q.text k
|
105
|
+
q.text " =>"
|
106
|
+
q.breakable " " unless v.trace?
|
107
|
+
q.pp v
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
else
|
112
|
+
q.pp value
|
113
|
+
q.group(0, " (", ")") do
|
114
|
+
q.seplist(source, lambda { q.breakable " " }, :each) do |k, v|
|
115
|
+
q.group do
|
116
|
+
q.text k.to_s
|
117
|
+
q.text "="
|
118
|
+
q.text v.to_s
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class << self
|
127
|
+
def capture
|
128
|
+
unless Settings.tracing_enabled
|
129
|
+
yield
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
trace = Trace.new
|
134
|
+
trace_stack.push trace
|
135
|
+
yield
|
136
|
+
trace_stack.last
|
137
|
+
ensure
|
138
|
+
trace_stack.pop
|
139
|
+
end
|
140
|
+
|
141
|
+
def trace_stack
|
142
|
+
(Thread.current[:__anyway__trace_stack__] ||= [])
|
143
|
+
end
|
144
|
+
|
145
|
+
def current_trace
|
146
|
+
trace_stack.last
|
147
|
+
end
|
148
|
+
|
149
|
+
alias tracing? current_trace
|
150
|
+
|
151
|
+
def source_stack
|
152
|
+
(Thread.current[:__anyway__trace_source_stack__] ||= [])
|
153
|
+
end
|
154
|
+
|
155
|
+
def current_trace_source
|
156
|
+
source_stack.last || accessor_source(caller_locations(2, 1).first)
|
157
|
+
end
|
158
|
+
|
159
|
+
def with_trace_source(src)
|
160
|
+
source_stack << src
|
161
|
+
yield
|
162
|
+
ensure
|
163
|
+
source_stack.pop
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def accessor_source(location)
|
169
|
+
{type: :accessor, called_from: location.path_lineno}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
module_function
|
174
|
+
|
175
|
+
def trace!(type, *path, **opts)
|
176
|
+
return yield unless Tracing.tracing?
|
177
|
+
source = {type: type}.merge(opts)
|
178
|
+
val = yield
|
179
|
+
if val.is_a?(Hash)
|
180
|
+
Tracing.current_trace.merge_values(val, **source)
|
181
|
+
else
|
182
|
+
Tracing.current_trace.record_value(yield, *path, **source)
|
183
|
+
end
|
184
|
+
val
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/lib/anyway/version.rb
CHANGED
data/lib/anyway_config.rb
CHANGED
@@ -1,32 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require "anyway/version"
|
5
|
-
|
6
|
-
require "anyway/config"
|
7
|
-
require "anyway/rails/config" if defined?(::Rails::VERSION)
|
8
|
-
require "anyway/env"
|
9
|
-
|
10
|
-
# Use Settings name to not confuse with Config.
|
11
|
-
#
|
12
|
-
# Settings contain the library-wide configuration.
|
13
|
-
class Settings
|
14
|
-
class << self
|
15
|
-
# Define whether to load data from
|
16
|
-
# *.yml.local (or credentials/local.yml.enc)
|
17
|
-
attr_accessor :use_local_files
|
18
|
-
end
|
3
|
+
require "ruby-next"
|
19
4
|
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
require "ruby-next/language/setup"
|
6
|
+
RubyNext::Language.setup_gem_load_path
|
7
|
+
|
8
|
+
require "anyway/version"
|
9
|
+
|
10
|
+
require "anyway/ext/deep_dup"
|
11
|
+
require "anyway/ext/deep_freeze"
|
12
|
+
require "anyway/ext/hash"
|
23
13
|
|
14
|
+
require "anyway/settings"
|
15
|
+
require "anyway/tracing"
|
16
|
+
require "anyway/config"
|
17
|
+
require "anyway/auto_cast"
|
18
|
+
require "anyway/env"
|
19
|
+
require "anyway/loaders"
|
20
|
+
|
21
|
+
module Anyway # :nodoc:
|
24
22
|
class << self
|
25
23
|
def env
|
26
24
|
@env ||= ::Anyway::Env.new
|
27
25
|
end
|
26
|
+
|
27
|
+
def loaders
|
28
|
+
@loaders ||= ::Anyway::Loaders::Registry.new
|
29
|
+
end
|
28
30
|
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
+
# Configure default loaders
|
33
|
+
loaders.append :yml, Loaders::YAML
|
34
|
+
loaders.append :env, Loaders::Env
|
32
35
|
end
|
36
|
+
|
37
|
+
require "anyway/rails" if defined?(::Rails::VERSION)
|
38
|
+
require "anyway/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
|