anyway_config 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0f3278ebf47801b6fdd2836c3f9f63d9a8fc1fe324f04e30f74d2dca179f471
4
- data.tar.gz: 8be597f8b2c6065f0ea96e5c58ec54d6ef3532f00d7a8639a963e99b2bba65c9
3
+ metadata.gz: fe4f769be22bc9cb820fd1159b88a565b2fb71e13ad617d90aaafbcb1673f995
4
+ data.tar.gz: 175e468716722b43b6c7d163a97b6523801f6658118d1c56bb5a4a0255807ef9
5
5
  SHA512:
6
- metadata.gz: 8174b86b24e86de09a55e473bcec74fc37aeca1c38bb2a75e83e71dbeebf512b68fdb93ae23d247cb6a8ae6c5d1e2fbb86d659bf611f83ad219fccabad1ba7b6
7
- data.tar.gz: dcf73ae92079a78b8c518cc3059a85848dd4934633269d016b402114279a0303108ab7962477ea2172ff15a1abc9c6401cea9106a83ba9b4c46a02220264eb23
6
+ metadata.gz: 7c323f89197ca35405ca77ee1e58a8b3fbbf4b0d6b57e7ccce380311a13994d18121966428a2681cb55a5892c2eff52ec347a6e3568072ea8f85d1cf7f75aa80
7
+ data.tar.gz: 25ee0b877318a2335c2019a414684d5b15e3755ceff90e51de3a0b821ee48f86a119f9753eaddac5dfb4724e38876ec5f18b68b065db7aa4b23d20b837666929
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 2.3.0 (2022-03-11)
6
+
7
+ - Add ability to load configurations under specific environments in pure Ruby apps. ([@fargelus][]).
8
+
9
+ Before loading environment configurations you need to specify variable `Anyway::Settings.current_environment`. In Rails apps this variable is same as `Rails.env` value.
10
+ After adding yaml loader will try to load params under this environment.
11
+
12
+ Also required env option was added to `Anyway::Config`.
13
+
14
+ ## 2.2.3 (2022-01-21)
15
+
16
+ - Fix Ruby 3.1 compatibility. ([@palkan][])
17
+
18
+ - Add ability to set default key for environmental YAML files. ([@skryukov])
19
+
20
+ Define a key for environmental yaml files to read default values from with `config.anyway_config.default_environmental_key = "default"`.
21
+ This way Anyway Config will try to read settings under the `"default"` key and then merge environmental settings into them.
22
+
23
+ ## 2.2.2 (2020-10-26)
24
+
25
+ - Fixed regression introduced by the `#deep_merge!` refinement.
26
+
5
27
  ## 2.2.1 (2020-09-28)
6
28
 
7
29
  - Minor fixes to the prev release.
@@ -435,3 +457,5 @@ No we're dependency-free!
435
457
  [@jastkand]: https://github.com/jastkand
436
458
  [@envek]: https://github.com/Envek
437
459
  [@progapandist]: https://github.com/progapandist
460
+ [@skryukov]: https://github.com/skryukov
461
+ [@fargelus]: https://github.com/fargelus
data/README.md CHANGED
@@ -297,6 +297,24 @@ end
297
297
  MyConfig.new(api_secret: "") #=> raises Anyway::Config::ValidationError
298
298
  ```
299
299
 
300
+ `Required` method supports additional `env` parameter which indicates necessity to run validations under specified
301
+ environments. `Env` parameter could be present in symbol, string, array or hash formats:
302
+
303
+ ```ruby
304
+ class EnvConfig < Anyway::Config
305
+ required :password, env: "production"
306
+ required :maps_api_key, env: :production
307
+ required :smtp_host, env: %i[production staging]
308
+ required :aws_bucket, env: %w[production staging]
309
+ required :anycable_rpc_host, env: {except: :development}
310
+ required :anycable_redis_url, env: {except: %i[development test]}
311
+ required :anycable_broadcast_adapter, env: {except: %w[development test]}
312
+ end
313
+ ```
314
+
315
+ If your current `Anyway::Settings.current_environment` is mismatch keys that specified
316
+ `Anyway::Config::ValidationError` error will be raised.
317
+
300
318
  If you need more complex validation or need to manipulate with config state right after it has been loaded, you can use _on load callbacks_ and `#raise_validation_error` method:
301
319
 
302
320
  ```ruby
@@ -373,6 +391,26 @@ staging:
373
391
  port: 3002 # This value will not be loaded at all
374
392
  ```
375
393
 
394
+ To provide default values you can use YAML anchors, but they do not deep-merge settings, so Anyway Config provides a way to define a special top-level key for default values like this:
395
+
396
+ ```ruby
397
+ config.anyway_config.default_environmental_key = "default"
398
+ ```
399
+
400
+ After that, Anyway Config will start reading settings under the `"default"` key and then merge environmental settings into them.
401
+
402
+ ```yml
403
+ default:
404
+ server: # This values will be loaded in all environments by default
405
+ host: localhost
406
+ port: 3002
407
+
408
+ staging:
409
+ server:
410
+ host: staging.example.com # This value will override the defaults when Rails.env.staging? is true
411
+ # port will be set to the value from the defaults — 3002
412
+ ```
413
+
376
414
  You can specify the lookup path for YAML files in one of the following ways:
377
415
 
378
416
  - By setting `config.anyway_config.default_config_path` to a target directory path:
@@ -509,7 +547,22 @@ The default data loading mechanism for non-Rails applications is the following (
509
547
 
510
548
  1) **YAML configuration files**: `./config/<config-name>.yml`.
511
549
 
512
- In pure Ruby apps, we do not know about _environments_ (`test`, `development`, `production`, etc.); thus, we assume that the YAML contains values for a single environment:
550
+ In pure Ruby apps, we also can load data under specific _environments_ (`test`, `development`, `production`, etc.).
551
+ If you want to enable this feature you must specify `Anyway::Settings.current_environment` variable for load config under specific environment.
552
+
553
+ ```ruby
554
+ Anyway::Settings.current_environment = "development"
555
+ ```
556
+
557
+ YAML files should be in this format:
558
+
559
+ ```yml
560
+ development:
561
+ host: localhost
562
+ port: 3000
563
+ ```
564
+
565
+ If `Anyway::Settings.current_environment` is missed we assume that the YAML contains values for a single environment:
513
566
 
514
567
  ```yml
515
568
  host: localhost
@@ -4,8 +4,8 @@ module Anyway
4
4
  module AutoCast
5
5
  # Regexp to detect array values
6
6
  # Array value is a values that contains at least one comma
7
- # and doesn't start/end with quote
8
- ARRAY_RXP = /\A[^'"].*\s*,\s*.*[^'"]\z/
7
+ # and doesn't start/end with quote or curly braces
8
+ ARRAY_RXP = /\A[^'"{].*\s*,\s*.*[^'"}]\z/
9
9
 
10
10
  class << self
11
11
  def call(val)
@@ -47,6 +47,8 @@ module Anyway # :nodoc:
47
47
  __type_caster__
48
48
  ].freeze
49
49
 
50
+ ENV_OPTION_EXCLUDE_KEY = :except
51
+
50
52
  class Error < StandardError; end
51
53
 
52
54
  class ValidationError < Error; end
@@ -126,14 +128,36 @@ module Anyway # :nodoc:
126
128
  end
127
129
  end
128
130
 
129
- def required(*names)
130
- unless (unknown_names = (names - config_attributes)).empty?
131
- raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}"
132
- end
131
+ def required(*names, env: nil)
132
+ unknown_names = names - config_attributes
133
+ raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
133
134
 
135
+ names = filter_by_env(names, env)
134
136
  required_attributes.push(*names)
135
137
  end
136
138
 
139
+ def filter_by_env(names, env)
140
+ return names if env.nil? || env.to_s == current_env
141
+
142
+ filtered_names = if env.is_a?(Hash)
143
+ names_with_exclude_env_option(names, env)
144
+ elsif env.is_a?(Array)
145
+ names if env.flat_map(&:to_s).include?(current_env)
146
+ end
147
+
148
+ filtered_names || []
149
+ end
150
+
151
+ def current_env
152
+ Settings.current_environment.to_s
153
+ end
154
+
155
+ def names_with_exclude_env_option(names, env)
156
+ envs = env[ENV_OPTION_EXCLUDE_KEY]
157
+ excluded_envs = [envs].flat_map(&:to_s)
158
+ names if excluded_envs.none?(current_env)
159
+ end
160
+
137
161
  def required_attributes
138
162
  return @required_attributes if instance_variable_defined?(:@required_attributes)
139
163
 
@@ -198,7 +222,7 @@ module Anyway # :nodoc:
198
222
  def new_empty_config() ; {}; end
199
223
 
200
224
  def coerce_types(mapping)
201
- coercion_mapping.deep_merge!(mapping)
225
+ Utils.deep_merge!(coercion_mapping, mapping)
202
226
  end
203
227
 
204
228
  def coercion_mapping
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "anyway/ext/hash"
5
+
6
+ using RubyNext
7
+ using Anyway::Ext::Hash
8
+
9
+ module Anyway
10
+ module Loaders
11
+ class YAML < Base
12
+ def call(config_path:, **_options)
13
+ rel_config_path = relative_config_path(config_path).to_s
14
+ base_config = trace!(:yml, path: rel_config_path) do
15
+ config = load_base_yml(config_path)
16
+ environmental?(config) ? config_with_env(config) : config
17
+ end
18
+
19
+ return base_config unless use_local?
20
+
21
+ local_path = local_config_path(config_path)
22
+ local_config = trace!(:yml, path: relative_config_path(local_path).to_s) { load_local_yml(local_path) }
23
+ Utils.deep_merge!(base_config, local_config)
24
+ end
25
+
26
+ private
27
+
28
+ def environmental?(parsed_yml)
29
+ # strange, but still possible
30
+ return true if Settings.default_environmental_key? && parsed_yml.key?(Settings.default_environmental_key)
31
+ # possible
32
+ return true if !Settings.future.unwrap_known_environments && Settings.current_environment
33
+ # for other environments
34
+ return true if Settings.known_environments&.any? { |_1| parsed_yml.key?(_1) }
35
+ # preferred
36
+ parsed_yml.key?(Settings.current_environment)
37
+ end
38
+
39
+ def config_with_env(config)
40
+ env_config = config[Settings.current_environment] || {}
41
+ return env_config unless Settings.default_environmental_key?
42
+
43
+ default_config = config[Settings.default_environmental_key] || {}
44
+ Utils.deep_merge!(default_config, env_config)
45
+ end
46
+
47
+ def parse_yml(path)
48
+ return {} unless File.file?(path)
49
+ require "yaml" unless defined?(::YAML)
50
+
51
+ # By default, YAML load will return `false` when the yaml document is
52
+ # empty. When this occurs, we return an empty hash instead, to match
53
+ # the interface when no config file is present.
54
+ begin
55
+ if defined?(ERB)
56
+ ::YAML.load(ERB.new(File.read(path)).result, aliases: true) || {} # rubocop:disable Security/YAMLLoad
57
+ else
58
+ ::YAML.load_file(path, aliases: true) || {}
59
+ end
60
+ rescue ArgumentError
61
+ if defined?(ERB)
62
+ ::YAML.load(ERB.new(File.read(path)).result) || {} # rubocop:disable Security/YAMLLoad
63
+ else
64
+ ::YAML.load_file(path) || {}
65
+ end
66
+ end
67
+ end
68
+
69
+ alias_method :load_base_yml, :parse_yml
70
+ alias_method :load_local_yml, :parse_yml
71
+
72
+ def local_config_path(path)
73
+ path.sub(/\.yml/, ".local.yml")
74
+ end
75
+
76
+ def relative_config_path(path)
77
+ Pathname.new(path).then do |path|
78
+ return path if path.relative?
79
+ path.relative_path_from(Settings.app_root)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  module Anyway
4
6
  # Use Settings name to not confuse with Config.
5
7
  #
@@ -35,6 +37,8 @@ module Anyway
35
37
  names.each { |_1| store[_1] = self.class.settings[_1] }
36
38
  end
37
39
 
40
+ setting :unwrap_known_environments, true
41
+
38
42
  private
39
43
 
40
44
  attr_reader :store
@@ -43,7 +47,10 @@ module Anyway
43
47
  class << self
44
48
  # Define whether to load data from
45
49
  # *.yml.local (or credentials/local.yml.enc)
46
- attr_accessor :use_local_files
50
+ attr_accessor :use_local_files,
51
+ :current_environment,
52
+ :default_environmental_key,
53
+ :known_environments
47
54
 
48
55
  # A proc returning a path to YML config file given the config name
49
56
  attr_reader :default_config_path
@@ -65,6 +72,14 @@ module Anyway
65
72
  def future
66
73
  @future ||= Future.new
67
74
  end
75
+
76
+ def app_root
77
+ Pathname.new(Dir.pwd)
78
+ end
79
+
80
+ def default_environmental_key?
81
+ !default_environmental_key.nil?
82
+ end
68
83
  end
69
84
 
70
85
  # By default, use local files only in development (that's the purpose if the local files)
@@ -6,12 +6,6 @@ module Anyway
6
6
  using Anyway::Ext::DeepDup
7
7
 
8
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
9
  refine Thread::Backtrace::Location do
16
10
  def path_lineno() ; "#{path}:#{lineno}"; end
17
11
  end
@@ -100,8 +94,15 @@ module Anyway
100
94
  q.group do
101
95
  q.text k
102
96
  q.text " =>"
103
- q.breakable " " unless v.trace?
104
- q.pp v
97
+ if v.trace?
98
+ q.text " { "
99
+ q.pp v
100
+ q.breakable " "
101
+ q.text "}"
102
+ else
103
+ q.breakable " "
104
+ q.pp v
105
+ end
105
106
  end
106
107
  end
107
108
  end
@@ -4,8 +4,8 @@ module Anyway
4
4
  module AutoCast
5
5
  # Regexp to detect array values
6
6
  # Array value is a values that contains at least one comma
7
- # and doesn't start/end with quote
8
- ARRAY_RXP = /\A[^'"].*\s*,\s*.*[^'"]\z/
7
+ # and doesn't start/end with quote or curly braces
8
+ ARRAY_RXP = /\A[^'"{].*\s*,\s*.*[^'"}]\z/
9
9
 
10
10
  class << self
11
11
  def call(val)
@@ -47,6 +47,8 @@ module Anyway # :nodoc:
47
47
  __type_caster__
48
48
  ].freeze
49
49
 
50
+ ENV_OPTION_EXCLUDE_KEY = :except
51
+
50
52
  class Error < StandardError; end
51
53
 
52
54
  class ValidationError < Error; end
@@ -126,14 +128,36 @@ module Anyway # :nodoc:
126
128
  end
127
129
  end
128
130
 
129
- def required(*names)
130
- unless (unknown_names = (names - config_attributes)).empty?
131
- raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}"
132
- end
131
+ def required(*names, env: nil)
132
+ unknown_names = names - config_attributes
133
+ raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
133
134
 
135
+ names = filter_by_env(names, env)
134
136
  required_attributes.push(*names)
135
137
  end
136
138
 
139
+ def filter_by_env(names, env)
140
+ return names if env.nil? || env.to_s == current_env
141
+
142
+ filtered_names = if env.is_a?(Hash)
143
+ names_with_exclude_env_option(names, env)
144
+ elsif env.is_a?(Array)
145
+ names if env.flat_map(&:to_s).include?(current_env)
146
+ end
147
+
148
+ filtered_names || []
149
+ end
150
+
151
+ def current_env
152
+ Settings.current_environment.to_s
153
+ end
154
+
155
+ def names_with_exclude_env_option(names, env)
156
+ envs = env[ENV_OPTION_EXCLUDE_KEY]
157
+ excluded_envs = [envs].flat_map(&:to_s)
158
+ names if excluded_envs.none?(current_env)
159
+ end
160
+
137
161
  def required_attributes
138
162
  return @required_attributes if instance_variable_defined?(:@required_attributes)
139
163
 
@@ -198,7 +222,7 @@ module Anyway # :nodoc:
198
222
  def new_empty_config() ; {}; end
199
223
 
200
224
  def coerce_types(mapping)
201
- coercion_mapping.deep_merge!(mapping)
225
+ Utils.deep_merge!(coercion_mapping, mapping)
202
226
  end
203
227
 
204
228
  def coercion_mapping
@@ -6,12 +6,6 @@ module Anyway
6
6
  using Anyway::Ext::DeepDup
7
7
 
8
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
9
  refine Thread::Backtrace::Location do
16
10
  def path_lineno() ; "#{path}:#{lineno}"; end
17
11
  end
@@ -100,8 +94,15 @@ module Anyway
100
94
  q.group do
101
95
  q.text k
102
96
  q.text " =>"
103
- q.breakable " " unless v.trace?
104
- q.pp v
97
+ if v.trace?
98
+ q.text " { "
99
+ q.pp v
100
+ q.breakable " "
101
+ q.text "}"
102
+ else
103
+ q.breakable " "
104
+ q.pp v
105
+ end
105
106
  end
106
107
  end
107
108
  end
@@ -47,6 +47,8 @@ module Anyway # :nodoc:
47
47
  __type_caster__
48
48
  ].freeze
49
49
 
50
+ ENV_OPTION_EXCLUDE_KEY = :except
51
+
50
52
  class Error < StandardError; end
51
53
 
52
54
  class ValidationError < Error; end
@@ -126,14 +128,36 @@ module Anyway # :nodoc:
126
128
  end
127
129
  end
128
130
 
129
- def required(*names)
130
- unless (unknown_names = (names - config_attributes)).empty?
131
- raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}"
132
- end
131
+ def required(*names, env: nil)
132
+ unknown_names = names - config_attributes
133
+ raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
133
134
 
135
+ names = filter_by_env(names, env)
134
136
  required_attributes.push(*names)
135
137
  end
136
138
 
139
+ def filter_by_env(names, env)
140
+ return names if env.nil? || env.to_s == current_env
141
+
142
+ filtered_names = if env.is_a?(Hash)
143
+ names_with_exclude_env_option(names, env)
144
+ elsif env.is_a?(Array)
145
+ names if env.flat_map(&:to_s).include?(current_env)
146
+ end
147
+
148
+ filtered_names || []
149
+ end
150
+
151
+ def current_env
152
+ Settings.current_environment.to_s
153
+ end
154
+
155
+ def names_with_exclude_env_option(names, env)
156
+ envs = env[ENV_OPTION_EXCLUDE_KEY]
157
+ excluded_envs = [envs].flat_map(&:to_s)
158
+ names if excluded_envs.none?(current_env)
159
+ end
160
+
137
161
  def required_attributes
138
162
  return @required_attributes if instance_variable_defined?(:@required_attributes)
139
163
 
@@ -198,7 +222,7 @@ module Anyway # :nodoc:
198
222
  def new_empty_config() = {}
199
223
 
200
224
  def coerce_types(mapping)
201
- coercion_mapping.deep_merge!(mapping)
225
+ Utils.deep_merge!(coercion_mapping, mapping)
202
226
  end
203
227
 
204
228
  def coercion_mapping
File without changes
@@ -6,12 +6,6 @@ module Anyway
6
6
  using Anyway::Ext::DeepDup
7
7
 
8
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
9
  refine Thread::Backtrace::Location do
16
10
  def path_lineno() = "#{path}:#{lineno}"
17
11
  end
@@ -100,8 +94,15 @@ module Anyway
100
94
  q.group do
101
95
  q.text k
102
96
  q.text " =>"
103
- q.breakable " " unless v.trace?
104
- q.pp v
97
+ if v.trace?
98
+ q.text " { "
99
+ q.pp v
100
+ q.breakable " "
101
+ q.text "}"
102
+ else
103
+ q.breakable " "
104
+ q.pp v
105
+ end
105
106
  end
106
107
  end
107
108
  end
@@ -4,8 +4,8 @@ module Anyway
4
4
  module AutoCast
5
5
  # Regexp to detect array values
6
6
  # Array value is a values that contains at least one comma
7
- # and doesn't start/end with quote
8
- ARRAY_RXP = /\A[^'"].*\s*,\s*.*[^'"]\z/
7
+ # and doesn't start/end with quote or curly braces
8
+ ARRAY_RXP = /\A[^'"{].*\s*,\s*.*[^'"}]\z/
9
9
 
10
10
  class << self
11
11
  def call(val)
data/lib/anyway/config.rb CHANGED
@@ -47,6 +47,8 @@ module Anyway # :nodoc:
47
47
  __type_caster__
48
48
  ].freeze
49
49
 
50
+ ENV_OPTION_EXCLUDE_KEY = :except
51
+
50
52
  class Error < StandardError; end
51
53
 
52
54
  class ValidationError < Error; end
@@ -126,14 +128,36 @@ module Anyway # :nodoc:
126
128
  end
127
129
  end
128
130
 
129
- def required(*names)
130
- unless (unknown_names = (names - config_attributes)).empty?
131
- raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}"
132
- end
131
+ def required(*names, env: nil)
132
+ unknown_names = names - config_attributes
133
+ raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
133
134
 
135
+ names = filter_by_env(names, env)
134
136
  required_attributes.push(*names)
135
137
  end
136
138
 
139
+ def filter_by_env(names, env)
140
+ return names if env.nil? || env.to_s == current_env
141
+
142
+ filtered_names = if env.is_a?(Hash)
143
+ names_with_exclude_env_option(names, env)
144
+ elsif env.is_a?(Array)
145
+ names if env.flat_map(&:to_s).include?(current_env)
146
+ end
147
+
148
+ filtered_names || []
149
+ end
150
+
151
+ def current_env
152
+ Settings.current_environment.to_s
153
+ end
154
+
155
+ def names_with_exclude_env_option(names, env)
156
+ envs = env[ENV_OPTION_EXCLUDE_KEY]
157
+ excluded_envs = [envs].flat_map(&:to_s)
158
+ names if excluded_envs.none?(current_env)
159
+ end
160
+
137
161
  def required_attributes
138
162
  return @required_attributes if instance_variable_defined?(:@required_attributes)
139
163
 
@@ -198,7 +222,7 @@ module Anyway # :nodoc:
198
222
  def new_empty_config() = {}
199
223
 
200
224
  def coerce_types(mapping)
201
- coercion_mapping.deep_merge!(mapping)
225
+ Utils.deep_merge!(coercion_mapping, mapping)
202
226
  end
203
227
 
204
228
  def coercion_mapping
@@ -26,16 +26,6 @@ module Anyway
26
26
  end
27
27
  hash[last_key] = val
28
28
  end
29
-
30
- def deep_merge!(other)
31
- other.each do |k, v|
32
- if key?(k) && self[k].is_a?(::Hash) && v.is_a?(::Hash)
33
- self[k].deep_merge!(v)
34
- else
35
- self[k] = v
36
- end
37
- end
38
- end
39
29
  end
40
30
 
41
31
  using self
@@ -10,18 +10,40 @@ module Anyway
10
10
  module Loaders
11
11
  class YAML < Base
12
12
  def call(config_path:, **_options)
13
- base_config = trace!(:yml, path: relative_config_path(config_path).to_s) { load_base_yml(config_path) }
13
+ rel_config_path = relative_config_path(config_path).to_s
14
+ base_config = trace!(:yml, path: rel_config_path) do
15
+ config = load_base_yml(config_path)
16
+ environmental?(config) ? config_with_env(config) : config
17
+ end
14
18
 
15
19
  return base_config unless use_local?
16
20
 
17
21
  local_path = local_config_path(config_path)
18
22
  local_config = trace!(:yml, path: relative_config_path(local_path).to_s) { load_local_yml(local_path) }
19
-
20
23
  Utils.deep_merge!(base_config, local_config)
21
24
  end
22
25
 
23
26
  private
24
27
 
28
+ def environmental?(parsed_yml)
29
+ # strange, but still possible
30
+ return true if Settings.default_environmental_key? && parsed_yml.key?(Settings.default_environmental_key)
31
+ # possible
32
+ return true if !Settings.future.unwrap_known_environments && Settings.current_environment
33
+ # for other environments
34
+ return true if Settings.known_environments&.any? { parsed_yml.key?(_1) }
35
+ # preferred
36
+ parsed_yml.key?(Settings.current_environment)
37
+ end
38
+
39
+ def config_with_env(config)
40
+ env_config = config[Settings.current_environment] || {}
41
+ return env_config unless Settings.default_environmental_key?
42
+
43
+ default_config = config[Settings.default_environmental_key] || {}
44
+ Utils.deep_merge!(default_config, env_config)
45
+ end
46
+
25
47
  def parse_yml(path)
26
48
  return {} unless File.file?(path)
27
49
  require "yaml" unless defined?(::YAML)
@@ -29,10 +51,18 @@ module Anyway
29
51
  # By default, YAML load will return `false` when the yaml document is
30
52
  # empty. When this occurs, we return an empty hash instead, to match
31
53
  # the interface when no config file is present.
32
- if defined?(ERB)
33
- ::YAML.load(ERB.new(File.read(path)).result) || {} # rubocop:disable Security/YAMLLoad
34
- else
35
- ::YAML.load_file(path) || {}
54
+ begin
55
+ if defined?(ERB)
56
+ ::YAML.load(ERB.new(File.read(path)).result, aliases: true) || {} # rubocop:disable Security/YAMLLoad
57
+ else
58
+ ::YAML.load_file(path, aliases: true) || {}
59
+ end
60
+ rescue ArgumentError
61
+ if defined?(ERB)
62
+ ::YAML.load(ERB.new(File.read(path)).result) || {} # rubocop:disable Security/YAMLLoad
63
+ else
64
+ ::YAML.load_file(path) || {}
65
+ end
36
66
  end
37
67
  end
38
68
 
@@ -46,7 +76,7 @@ module Anyway
46
76
  def relative_config_path(path)
47
77
  Pathname.new(path).then do |path|
48
78
  return path if path.relative?
49
- path.relative_path_from(Pathname.new(Dir.pwd))
79
+ path.relative_path_from(Settings.app_root)
50
80
  end
51
81
  end
52
82
  end
@@ -3,28 +3,7 @@
3
3
  module Anyway
4
4
  module Rails
5
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? { 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
6
+ class YAML < Anyway::Loaders::YAML; end
28
7
  end
29
8
  end
30
9
  end
@@ -9,13 +9,8 @@ end
9
9
 
10
10
  module Anyway
11
11
  class Settings
12
- class Future
13
- setting :unwrap_known_environments, true
14
- end
15
-
16
12
  class << self
17
13
  attr_reader :autoload_static_config_path, :autoloader
18
- attr_accessor :known_environments
19
14
 
20
15
  if defined?(::Zeitwerk)
21
16
  def autoload_static_config_path=(val)
@@ -59,9 +54,19 @@ module Anyway
59
54
  :no_op
60
55
  end
61
56
  end
57
+
58
+ def current_environment
59
+ ::Rails.env.to_s
60
+ end
61
+
62
+ def app_root
63
+ ::Rails.root
64
+ end
62
65
  end
63
66
 
64
67
  self.default_config_path = ->(name) { ::Rails.root.join("config", "#{name}.yml") }
65
68
  self.known_environments = %w[test development production]
69
+ # Don't try read defaults when no key defined
70
+ self.default_environmental_key = nil
66
71
  end
67
72
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  module Anyway
4
6
  # Use Settings name to not confuse with Config.
5
7
  #
@@ -35,6 +37,8 @@ module Anyway
35
37
  names.each { store[_1] = self.class.settings[_1] }
36
38
  end
37
39
 
40
+ setting :unwrap_known_environments, true
41
+
38
42
  private
39
43
 
40
44
  attr_reader :store
@@ -43,7 +47,10 @@ module Anyway
43
47
  class << self
44
48
  # Define whether to load data from
45
49
  # *.yml.local (or credentials/local.yml.enc)
46
- attr_accessor :use_local_files
50
+ attr_accessor :use_local_files,
51
+ :current_environment,
52
+ :default_environmental_key,
53
+ :known_environments
47
54
 
48
55
  # A proc returning a path to YML config file given the config name
49
56
  attr_reader :default_config_path
@@ -65,6 +72,14 @@ module Anyway
65
72
  def future
66
73
  @future ||= Future.new
67
74
  end
75
+
76
+ def app_root
77
+ Pathname.new(Dir.pwd)
78
+ end
79
+
80
+ def default_environmental_key?
81
+ !default_environmental_key.nil?
82
+ end
68
83
  end
69
84
 
70
85
  # By default, use local files only in development (that's the purpose if the local files)
@@ -6,12 +6,6 @@ module Anyway
6
6
  using Anyway::Ext::DeepDup
7
7
 
8
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
9
  refine Thread::Backtrace::Location do
16
10
  def path_lineno() = "#{path}:#{lineno}"
17
11
  end
@@ -86,7 +80,7 @@ module Anyway
86
80
  if trace?
87
81
  value.transform_values(&:to_h).tap { _1.default_proc = nil }
88
82
  else
89
- {value, source}
83
+ {value:, source:}
90
84
  end
91
85
  end
92
86
 
@@ -100,8 +94,15 @@ module Anyway
100
94
  q.group do
101
95
  q.text k
102
96
  q.text " =>"
103
- q.breakable " " unless v.trace?
104
- q.pp v
97
+ if v.trace?
98
+ q.text " { "
99
+ q.pp v
100
+ q.breakable " "
101
+ q.text "}"
102
+ else
103
+ q.breakable " "
104
+ q.pp v
105
+ end
105
106
  end
106
107
  end
107
108
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Anyway # :nodoc:
4
- VERSION = "2.2.1"
4
+ VERSION = "2.3.0"
5
5
  end
@@ -4,7 +4,7 @@
4
4
  class ApplicationConfig < Anyway::Config
5
5
  class << self
6
6
  # Make it possible to access a singleton config instance
7
- # via class methods (i.e., without explictly calling `instance`)
7
+ # via class methods (i.e., without explicitly calling `instance`)
8
8
  delegate_missing_to :instance
9
9
 
10
10
  private
@@ -3,8 +3,11 @@ module Anyway
3
3
  def self.loaders: -> Loaders::Registry
4
4
 
5
5
  class Settings
6
- def self.default_config_path=: (^(untyped) -> String val) -> ^(untyped) -> String?
6
+ def self.default_config_path=: (String | Pathname | ^(untyped) -> String val) -> void
7
7
  def self.future: -> Future
8
+ def self.current_environment: -> String?
9
+ def self.default_environmental_key: -> String?
10
+ def self.known_environments: -> Array[String]?
8
11
 
9
12
  class Future
10
13
  def self.setting: (untyped name, untyped default_value) -> untyped
@@ -19,14 +22,14 @@ module Anyway
19
22
  end
20
23
 
21
24
  def inspect: -> String
22
- def self.capture: ?{ -> Hash[untyped, untyped] } -> nil
25
+ def self.capture: ?{ -> Hash[untyped, untyped]? } -> Trace
23
26
  def self.trace_stack: -> Array[untyped]
24
27
  def self.current_trace: -> Trace?
25
28
  def self.source_stack: -> Array[untyped]
26
- def self.current_trace_source: -> {type: :accessor, called_from: untyped}
27
- def self.with_trace_source: (untyped src) -> untyped
28
- def trace!: (Symbol, *Array[String] paths, **untyped) ?{ -> Hash[untyped, untyped]} -> Hash[untyped, untyped]
29
- def self.trace!: (Symbol, *Array[String] paths, **untyped) ?{ -> Hash[untyped, untyped]} -> Hash[untyped, untyped]
29
+ def self.current_trace_source: -> ({type: Symbol} & Hash[Symbol, untyped])
30
+ def self.with_trace_source: (untyped src) { -> void } -> untyped
31
+ def trace!: [V] (Symbol, *String paths, **untyped) ?{ -> V} -> V
32
+ def self.trace!: [V] (Symbol, *String paths, **untyped) ?{ -> V} -> V
30
33
  end
31
34
 
32
35
  module RBSGenerator
@@ -56,6 +59,7 @@ module Anyway
56
59
  type hashType = Hash[Symbol, valueType | arrayType | hashType]
57
60
 
58
61
  type mappingType = valueType | arrayType | hashType
62
+ type envType = String | Symbol | Array[String | Symbol] | {except: String | Symbol | Array[String | Symbol]}
59
63
 
60
64
  class Config
61
65
  extend RBSGenerator
@@ -67,7 +71,7 @@ module Anyway
67
71
  def self.attr_config: (*Symbol args, **untyped) -> void
68
72
  def self.defaults: -> Hash[String, untyped]
69
73
  def self.config_attributes: -> Array[Symbol]?
70
- def self.required: (*Symbol names) -> void
74
+ def self.required: (*Symbol names, ?env: envType) -> void
71
75
  def self.required_attributes: -> Array[Symbol]
72
76
  def self.on_load: (*Symbol callbacks) ?{ () -> void } -> void
73
77
  def self.config_name: (?(Symbol | String) val) -> String?
@@ -80,9 +84,11 @@ module Anyway
80
84
  attr_reader env_prefix: String
81
85
 
82
86
  def initialize: (?Hash[Symbol | String, untyped] overrides) -> void
87
+ | (NilClass) -> void
83
88
  def reload: (?Hash[Symbol | String, untyped] overrides) -> Config
84
89
  def clear: -> void
85
90
  def load: (Hash[Symbol | String, untyped] overrides) -> Config
91
+ | (NilClass) -> Config
86
92
  def dig: (*(Symbol | String) keys) -> untyped
87
93
  def to_h: -> Hash[untyped, untyped]
88
94
  def dup: -> Config
@@ -117,12 +123,21 @@ module Anyway
117
123
  def use_local?: -> bool
118
124
  end
119
125
 
126
+ interface _Loader
127
+ def call: (**untyped) -> Hash[untyped, untyped]
128
+ end
129
+
120
130
  class Registry
121
- def prepend: (Symbol id, Base loader) -> void
122
- def append: (Symbol id, Base loader) -> void
123
- def insert_before: (Symbol another_id, Symbol id, Base loader) -> void
124
- def insert_after: (Symbol another_id, Symbol id, Base loader) -> void
125
- def override: (Symbol id, Base loader) -> void
131
+ def prepend: (Symbol id, _Loader loader) -> void
132
+ | (Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
133
+ def append: (Symbol id, _Loader loader) -> void
134
+ | (Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
135
+ def insert_before: (Symbol another_id, Symbol id, _Loader loader) -> void
136
+ | (Symbol another_id, Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
137
+ def insert_after: (Symbol another_id, Symbol id, _Loader loader) -> void
138
+ | (Symbol another_id, Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
139
+ def override: (Symbol id, _Loader loader) -> void
140
+ | (Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
126
141
  def delete: (Symbol id) -> void
127
142
  end
128
143
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anyway_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-28 00:00:00.000000000 Z
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-next-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.11.0
19
+ version: 0.14.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.11.0
26
+ version: 0.14.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: ammeter
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -86,28 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '0.8'
89
+ version: 0.14.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '0.8'
97
- - !ruby/object:Gem::Dependency
98
- name: steep
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
96
+ version: 0.14.0
111
97
  description: "\n Configuration DSL for Ruby libraries and applications.\n Allows
112
98
  you to easily follow the twelve-factor application principles (https://12factor.net/config).\n
113
99
  \ "
@@ -120,14 +106,9 @@ files:
120
106
  - CHANGELOG.md
121
107
  - LICENSE.txt
122
108
  - README.md
123
- - lib/.rbnext/1995.next/anyway/config.rb
124
- - lib/.rbnext/1995.next/anyway/dynamic_config.rb
125
- - lib/.rbnext/1995.next/anyway/env.rb
126
- - lib/.rbnext/1995.next/anyway/loaders/base.rb
127
- - lib/.rbnext/1995.next/anyway/tracing.rb
128
109
  - lib/.rbnext/2.7/anyway/auto_cast.rb
129
110
  - lib/.rbnext/2.7/anyway/config.rb
130
- - lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb
111
+ - lib/.rbnext/2.7/anyway/loaders/yaml.rb
131
112
  - lib/.rbnext/2.7/anyway/rbs.rb
132
113
  - lib/.rbnext/2.7/anyway/settings.rb
133
114
  - lib/.rbnext/2.7/anyway/tracing.rb
@@ -137,6 +118,11 @@ files:
137
118
  - lib/.rbnext/3.0/anyway/loaders.rb
138
119
  - lib/.rbnext/3.0/anyway/loaders/base.rb
139
120
  - lib/.rbnext/3.0/anyway/tracing.rb
121
+ - lib/.rbnext/3.1/anyway/config.rb
122
+ - lib/.rbnext/3.1/anyway/dynamic_config.rb
123
+ - lib/.rbnext/3.1/anyway/env.rb
124
+ - lib/.rbnext/3.1/anyway/loaders/base.rb
125
+ - lib/.rbnext/3.1/anyway/tracing.rb
140
126
  - lib/anyway.rb
141
127
  - lib/anyway/auto_cast.rb
142
128
  - lib/anyway/config.rb
@@ -1,30 +0,0 @@
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