anyway_config 2.0.0.pre2 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +350 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +444 -119
  5. data/lib/.rbnext/2.7/anyway/auto_cast.rb +33 -0
  6. data/lib/.rbnext/2.7/anyway/config.rb +404 -0
  7. data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +31 -0
  8. data/lib/.rbnext/2.7/anyway/tracing.rb +187 -0
  9. data/lib/anyway/auto_cast.rb +33 -0
  10. data/lib/anyway/config.rb +235 -58
  11. data/lib/anyway/dynamic_config.rb +1 -1
  12. data/lib/anyway/env.rb +29 -19
  13. data/lib/anyway/ext/hash.rb +14 -6
  14. data/lib/anyway/loaders.rb +79 -0
  15. data/lib/anyway/loaders/base.rb +23 -0
  16. data/lib/anyway/loaders/env.rb +16 -0
  17. data/lib/anyway/loaders/yaml.rb +46 -0
  18. data/lib/anyway/option_parser_builder.rb +6 -3
  19. data/lib/anyway/optparse_config.rb +10 -6
  20. data/lib/anyway/rails.rb +16 -0
  21. data/lib/anyway/rails/config.rb +2 -69
  22. data/lib/anyway/rails/loaders.rb +5 -0
  23. data/lib/anyway/rails/loaders/credentials.rb +64 -0
  24. data/lib/anyway/rails/loaders/secrets.rb +39 -0
  25. data/lib/anyway/rails/loaders/yaml.rb +19 -0
  26. data/lib/anyway/rails/settings.rb +58 -0
  27. data/lib/anyway/railtie.rb +14 -2
  28. data/lib/anyway/settings.rb +29 -0
  29. data/lib/anyway/tracing.rb +187 -0
  30. data/lib/anyway/version.rb +1 -1
  31. data/lib/anyway_config.rb +27 -21
  32. data/lib/generators/anyway/app_config/USAGE +9 -0
  33. data/lib/generators/anyway/app_config/app_config_generator.rb +17 -0
  34. data/lib/generators/anyway/config/USAGE +13 -0
  35. data/lib/generators/anyway/config/config_generator.rb +46 -0
  36. data/lib/generators/anyway/config/templates/config.rb.tt +9 -0
  37. data/lib/generators/anyway/config/templates/config.yml.tt +13 -0
  38. data/lib/generators/anyway/install/USAGE +4 -0
  39. data/lib/generators/anyway/install/install_generator.rb +43 -0
  40. data/lib/generators/anyway/install/templates/application_config.rb.tt +17 -0
  41. metadata +75 -10
  42. data/lib/anyway/ext/string_serialize.rb +0 -38
  43. data/lib/anyway/loaders/env_loader.rb +0 -0
  44. data/lib/anyway/loaders/secrets_loader.rb +0 -0
  45. data/lib/anyway/loaders/yaml_loader.rb +0 -0
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyway/rails/loaders/yaml"
4
+ require "anyway/rails/loaders/secrets"
5
+ require "anyway/rails/loaders/credentials"
@@ -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
@@ -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
- # Allow autoloading of app/configs in configuration files
9
- ActiveSupport::Dependencies.autoload_paths << "app/configs"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Anyway # :nodoc:
4
- VERSION = "2.0.0.pre2"
4
+ VERSION = "2.0.0.rc1"
5
5
  end
data/lib/anyway_config.rb CHANGED
@@ -1,32 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Anyway # :nodoc:
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
- # 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
- end
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
- require "anyway/railtie" if defined?(::Rails::VERSION)
31
- require "anyway/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
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"