anyway_config 1.4.4 → 2.0.0.pre

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +187 -51
  3. data/lib/anyway.rb +22 -2
  4. data/lib/anyway/config.rb +116 -98
  5. data/lib/anyway/dynamic_config.rb +27 -0
  6. data/lib/anyway/env.rb +2 -5
  7. data/lib/anyway/ext/deep_dup.rb +4 -4
  8. data/lib/anyway/ext/deep_freeze.rb +5 -0
  9. data/lib/anyway/ext/jruby.rb +9 -4
  10. data/lib/anyway/ext/string_serialize.rb +1 -7
  11. data/{spec/dummy/config.ru → lib/anyway/loaders/env_loader.rb} +0 -0
  12. data/lib/anyway/loaders/secrets_loader.rb +0 -0
  13. data/lib/anyway/loaders/yaml_loader.rb +0 -0
  14. data/lib/anyway/option_parser_builder.rb +2 -2
  15. data/lib/anyway/optparse_config.rb +90 -0
  16. data/lib/anyway/rails/config.rb +76 -24
  17. data/lib/anyway/railtie.rb +11 -0
  18. data/lib/anyway/testing.rb +13 -0
  19. data/lib/anyway/testing/helpers.rb +36 -0
  20. data/lib/anyway/version.rb +1 -1
  21. metadata +46 -48
  22. data/.gem_release.yml +0 -3
  23. data/.gitignore +0 -40
  24. data/.rubocop.yml +0 -50
  25. data/.travis.yml +0 -30
  26. data/CHANGELOG.md +0 -112
  27. data/Gemfile +0 -21
  28. data/Rakefile +0 -23
  29. data/anyway_config.gemspec +0 -29
  30. data/config/cool.yml +0 -5
  31. data/gemfiles/jruby.gemfile +0 -7
  32. data/gemfiles/rails42.gemfile +0 -6
  33. data/gemfiles/rails5.gemfile +0 -6
  34. data/gemfiles/railsmaster.gemfile +0 -7
  35. data/spec/anyway.yml +0 -2
  36. data/spec/config_spec.rb +0 -337
  37. data/spec/config_spec_norails.rb +0 -86
  38. data/spec/dummy/config/application.rb +0 -13
  39. data/spec/dummy/config/boot.rb +0 -5
  40. data/spec/dummy/config/cool.yml +0 -5
  41. data/spec/dummy/config/cool_custom.yml +0 -2
  42. data/spec/dummy/config/database.yml +0 -25
  43. data/spec/dummy/config/environment.rb +0 -5
  44. data/spec/dummy/config/environments/test.rb +0 -2
  45. data/spec/dummy/config/routes.rb +0 -2
  46. data/spec/dummy/config/secrets.yml +0 -30
  47. data/spec/env_spec.rb +0 -50
  48. data/spec/ext/deep_dup_spec.rb +0 -38
  49. data/spec/ext/deep_freeze_spec.rb +0 -32
  50. data/spec/ext/hash_spec.rb +0 -39
  51. data/spec/ext/string_serialize_spec.rb +0 -32
  52. data/spec/spec_helper.rb +0 -31
  53. data/spec/spec_norails_helper.rb +0 -26
  54. data/spec/support/cool_config.rb +0 -10
  55. data/spec/support/print_warning_matcher.rb +0 -34
  56. data/spec/support/small_config.rb +0 -7
  57. data/spec/support/test_config.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af77cb70b9fea95ccb938643fcf462f04ef0087a1b3811de9e9f66e696f29ad5
4
- data.tar.gz: 771eeb316a5a11d9bd8072dd7f506ddd5a02d36995ccf2facec3f40dcbc412ea
3
+ metadata.gz: 2d8ed2249df0baa740893d459922e839793eab4cef51b9410e2c618393ba88a7
4
+ data.tar.gz: 587d0578b716af5035e393ab29eabff5aea163d44f705ed9b245eb51d083b4f4
5
5
  SHA512:
6
- metadata.gz: 87d5c17d6ec017474112df69685edcdfe9c7547ae7ad82b4338a4dd3493afeef50d6102fe87e7b0a5070aa6ad48f4872371515f32443a443bc324a67dcdfd918
7
- data.tar.gz: 7c4cb678483573bb6a4db45ea5c78bbf1d077fcc7e5486fe4e420cae2c4de09224611d1925cdddebdc80ea767e140870b81bbcf39ce2be9515761846db70cc13
6
+ metadata.gz: 5fb7bcf6b692b6fc75f4eb0ca3170787defac2063d646ca8c9ec77d3563e526c58dc16af77e42bf6d293da21d8303d6d82afca723e2b97e528b243702f1c38b5
7
+ data.tar.gz: b89c278a7436c024f61894f59c885704c2cf4ae539bb382825181fe13d5f787662c6aafdc39b7d49ba9f4ee1c9535128e3fadb7904016c790907010e165996b9
data/README.md CHANGED
@@ -3,7 +3,10 @@
3
3
 
4
4
  # Anyway Config
5
5
 
6
- Rails/Ruby plugin/application configuration tool which allows you to load parameters from different sources: YAML, Rails secrets, environment.
6
+ **NOTE:** this readme shows doc for the upcoming 2.0 version (`2.0.0.pre` is available on RubyGems).
7
+ For version 1.x see [1-4-stable branch](https://github.com/palkan/anyway_config/tree/1-4-stable).
8
+
9
+ Rails/Ruby plugin/application configuration tool which allows you to load parameters from different sources: YAML, Rails secrets/credentials, environment.
7
10
 
8
11
  Allows you to easily follow the [twelve-factor application](https://12factor.net/config) principles and adds zero complexity to your development process.
9
12
 
@@ -15,6 +18,8 @@ Libraries using Anyway Config:
15
18
 
16
19
  - [Sniffer](https://github.com/aderyabin/sniffer)
17
20
 
21
+ - [Blood Contracts](https://github.com/sclinede/blood_contracts)
22
+
18
23
  - [and others](https://github.com/palkan/anyway_config/network/dependents).
19
24
 
20
25
  ## Installation
@@ -24,9 +29,9 @@ Libraries using Anyway Config:
24
29
  ```ruby
25
30
  # my-cool-gem.gemspec
26
31
  Gem::Specification.new do |spec|
27
- ...
28
- spec.add_dependency "anyway_config", "~> 1.0"
29
- ...
32
+ # ...
33
+ spec.add_dependency "anyway_config", "2.0.0.pre"
34
+ # ...
30
35
  end
31
36
  ```
32
37
 
@@ -34,7 +39,7 @@ end
34
39
 
35
40
  ```ruby
36
41
  # Gemfile
37
- gem "anyway_config", "~> 1.0"
42
+ gem "anyway_config", "2.0.0.pre"
38
43
  ```
39
44
 
40
45
  3) Install globally:
@@ -50,11 +55,11 @@ $ gem install anyway_config
50
55
  Create configuration class:
51
56
 
52
57
  ```ruby
53
- require 'anyway'
58
+ require "anyway"
54
59
 
55
60
  module MyCoolGem
56
61
  class Config < Anyway::Config
57
- attr_config user: 'root', password: 'root', host: 'localhost'
62
+ attr_config user: "root", password: "root", host: "localhost"
58
63
  end
59
64
  end
60
65
  ```
@@ -62,7 +67,7 @@ end
62
67
  `attr_config` creates accessors and default values. If you don't need default values just write:
63
68
 
64
69
  ```ruby
65
- attr_config :user, :password, host: 'localhost'
70
+ attr_config :user, :password, host: "localhost", options: {}
66
71
  ```
67
72
 
68
73
  Then create an instance of the config class and use it:
@@ -74,33 +79,43 @@ module MyCoolGem
74
79
  end
75
80
  end
76
81
 
77
- MyCoolGem.config.user #=> 'root'
82
+ MyCoolGem.config.user #=> "root"
78
83
  ```
79
84
 
80
- #### Customize name
85
+ #### Config name
86
+
87
+ Anyway Config relies on the notion of _config name_ to populate data.
88
+
89
+ By default, Anyway Config uses the config class name to infer the config name using the following rules:
90
+ - if the class name has a form of `<Module>::Config` then use the module name (`SomeModule::Config => "somemodule"`)
91
+ - if the class name has a form of `<Something>Config` then use the class name prefix (`SomeConfig => "some"`)
81
92
 
82
- By default, Anyway Config uses the namespace (the outer module name) as the config name, but you can set it manually:
93
+ **NOTE:** in both cases the config name is a **downcased** module/class prefix, not underscored.
94
+
95
+ You can also specify the config name explicitly (it's required in cases when you class name doesn't match any of the patterns above):
83
96
 
84
97
  ```ruby
85
98
  module MyCoolGem
86
99
  class Config < Anyway::Config
87
100
  config_name :cool
88
- attr_config user: 'root', password: 'root', host: 'localhost', options: {}
101
+ attr_config user: "root", password: "root", host: "localhost", options: {}
89
102
  end
90
103
  end
91
104
  ```
92
105
 
93
106
  #### Customize env variable names prefix
94
107
 
95
- By default, Anyway Config uses underscored config name as a prefix for env variable names (e.g.
96
- `config_name :my_app` will result to parsing `MY_APP_HOST` variable). You can set env prefix
97
- explicitly, and it will be used as is:
108
+ By default, Anyway Config uses upcased config name as a prefix for env variable names (e.g.
109
+ `config_name :my_app` will result to parsing `MY_APP_` prefix).
110
+
111
+ You can set env prefix explicitly:
98
112
 
99
113
  ```ruby
100
114
  module MyCoolGem
101
115
  class Config < Anyway::Config
116
+ config_name :cool_gem
102
117
  env_prefix :really_cool # now variables, starting wih `REALLY_COOL_`, will be parsed
103
- attr_config user: 'root', password: 'root', host: 'localhost', options: {}
118
+ attr_config user: "root", password: "root", host: "localhost", options: {}
104
119
  end
105
120
  end
106
121
  ```
@@ -108,18 +123,16 @@ end
108
123
  #### Provide explicit values
109
124
 
110
125
  Sometimes it's useful to set some parameters explicitly during config initialization.
111
- You can do that using `overrides` option:
126
+ You can do that by passing a Hash into `.new` method:
112
127
 
113
128
  ```ruby
114
129
  config = MyCoolGem::Config.new(
115
- overrides: {
116
- user: 'john',
117
- password: 'rubyisnotdead'
118
- }
130
+ user: "john",
131
+ password: "rubyisnotdead"
119
132
  )
120
133
 
121
134
  # The value would not be overriden from other sources (such as YML file, env)
122
- config.user == 'john'
135
+ config.user == "john"
123
136
  ```
124
137
 
125
138
  ### Dynamic configuration
@@ -127,32 +140,121 @@ config.user == 'john'
127
140
  You can also create configuration objects without pre-defined schema (just like `Rails.application.config_for` but more [powerful](#railsapplicationconfig_for-vs-anywayconfigfor)):
128
141
 
129
142
  ```ruby
130
- # load data from config/my_app.yml, secrets.my_app (if using Rails), ENV["MYAPP_*"]
143
+ # load data from config/my_app.yml, secrets.my_app (if using Rails), ENV["MY_APP_*"]
144
+ # MY_APP_VALUE=42
131
145
  config = Anyway::Config.for(:my_app)
146
+ config.value #=> 42
147
+
148
+ # you can specify the config file path or env prefix
149
+ config = Anyway::Config.for(:my_app, config_path: "my_config.yml", env_prefix: "MYAPP")
132
150
  ```
133
151
 
134
152
  ### Using with Rails
135
153
 
154
+ **NOTE:** version 2.x supports Rails >= 5.0; for Rails 4.x use version 1.x of the gem.
155
+
136
156
  Your config will be filled up with values from the following sources (ordered by priority from low to high):
137
157
 
138
- - `RAILS_ROOT/config/my_cool_gem.yml` (for the current `RAILS_ENV`, supports `ERB`). You can override this setting
139
- through special environment variable – 'MYCOOLGEM_CONF' – containing the path to the YAML file.
158
+ - `RAILS_ROOT/config/my_cool_gem.yml` (for the current `RAILS_ENV`, supports `ERB`):
140
159
 
141
- - `Rails.application.secrets.my_cool_gem`
160
+ ```yml
161
+ test:
162
+ host: localhost
163
+ port: 3002
164
+
165
+ development:
166
+ host: localhost
167
+ port: 3000
168
+ ```
169
+
170
+ **NOTE:** you can override the default YML lookup path by setting `MYCOOLGEM_CONF` env variable.
171
+
172
+ - `Rails.application.secrets.my_cool_gem` (if `secrets.yml` present):
173
+
174
+ ```yml
175
+ # config/secrets.yml
176
+ development:
177
+ my_cool_gem:
178
+ port: 4444
179
+ ```
180
+
181
+ - `Rails.application.credentials` (if supported):
182
+
183
+ ```yml
184
+ my_cool_gem:
185
+ host: secret.host
186
+ ```
142
187
 
143
188
  - `ENV['MYCOOLGEM_*']`.
144
189
 
190
+ #### `app/configs`
191
+
192
+ You can store application-level config classes in `app/configs` folder.
193
+
194
+ Anyway Config automatically adds this folder to Rails autoloading system to make it possible to
195
+ autoload configs even during the configuration phase.
196
+
197
+ Consider an example: setting the Action Mailer host name for Heroku review apps.
198
+
199
+ We have the following config to fetch the Heroku provided [metadata](https://devcenter.heroku.com/articles/dyno-metadata):
200
+
201
+ ```ruby
202
+ # This data is provided by Heroku Dyno Metadadata add-on.
203
+ class HerokuConfig < Anyway::Config
204
+ attr_config :app_id, :app_name,
205
+ :dyno_id, :release_version,
206
+ :slug_commit
207
+
208
+ def hostname
209
+ "#{app_name}.herokuapp.com"
210
+ end
211
+ end
212
+ ```
213
+
214
+ Then in `config/application.rb` you can do the following:
215
+
216
+ ```ruby
217
+ config.action_mailer.default_url_options = {host: HerokuConfig.new.hostname}
218
+ ```
219
+
145
220
  ### Using with Ruby
146
221
 
147
- By default, Anyway Config is looking for a config YAML at `./config/<config-name>.yml` e.g. `./config/my_cool_gem.yml`.
148
- You can override this location the same way as for Rails.
222
+ When you're using Anyway Config in non-Rails environment, we're looking for a YANL config file
223
+ at `./config/<config-name>.yml`.
149
224
 
150
- Environmental variables work the same way too.
225
+ You can override this setting through special environment variable – 'MYCOOLGEM_CONF' – containing the path to the YAML file.
151
226
 
227
+ **NOTE:** in pure Ruby apps we have no knowledge of _environments_ (`test`, `development`, `production`, etc.); thus we assume that the YAML contains values for a single environment:
152
228
 
153
- ### Config clear and reload
229
+ ```yml
230
+ host: localhost
231
+ port: 3000
232
+ ```
233
+
234
+ Environmental variables work the same way as with Rails.
235
+
236
+ ### Local files
237
+
238
+ It's useful to have personal, user-specific configuration in development, which extends the project-wide one.
239
+
240
+ We support this by looking at _local_ files when loading the configuration data:
241
+ - `<config_name>.local.yml` files (next to\* the _global_ `<config_name>.yml`)
242
+ - `config/credentials/local.yml.enc` (for Rails >= 6, generate it via `rails credentials:edit --environment local`).
243
+
244
+ \* If the YAML config path is not default (i.e. set via `<CONFIG_NAME>_CONF`), we lookup the local
245
+ config at this location, too.
246
+
247
+ Local configs are meant for using in development and only loaded if `Anyway::Settings.use_local_files` is `true` (which is true by default if `RACK_ENV` or `RAILS_ENV` env variable is equal to `"development"`).
248
+
249
+ **NOTE:** in Rails apps you can use `Rails.applicaiton.configuration.anyway_config.use_local_files`.
154
250
 
155
- There are `#clear` and `#reload` functions on your config (which do exactly what they state).
251
+ Don't forget to add `*.local.yml` (and `config/credentials/local.*`) to your `.gitignore`.
252
+
253
+ **NOTE:** local YAML configs for Rails app must be environment-free (i.e. you shouldn't have top-level `development:` key).
254
+
255
+ ### Reload configuration
256
+
257
+ There are `#clear` and `#reload` methods which do exactly what they state.
156
258
 
157
259
  Note: `#reload` also accepts `overrides` key to provide explicit values (see above).
158
260
 
@@ -194,7 +296,7 @@ end
194
296
 
195
297
  config = MyConfig.new
196
298
 
197
- config.parse_options!(%w(--host localhost --port 3333 --log-level debug))
299
+ config.parse_options!(%w[--host localhost --port 3333 --log-level debug])
198
300
 
199
301
  config.host # => "localhost"
200
302
  config.port # => 3333
@@ -211,10 +313,12 @@ Rails 4.2 introduced new feature: `Rails.application.config_for`. It looks very
211
313
 
212
314
  | Feature | Rails | Anyway Config |
213
315
  | ------------- |:-------------:| -----:|
214
- | load data from `config/app.yml` | yes | yes |
215
- | load data from `secrets` | no | yes |
216
- | load data from environment | no | yes |
217
- | return Hash with indifferent access | no | yes |
316
+ | load data from `config/app.yml` | yes | yes |
317
+ | load data from `secrets` | no | yes |
318
+ | load data from `credentials` | no | yes |
319
+ | load data from environment | no | yes |
320
+ | local config files | no | yes |
321
+ | return Hash with indifferent access | no | yes |
218
322
  | support ERB within `config/app.yml` | yes | yes* |
219
323
  | raise errors if file doesn't exist | yes | no |
220
324
 
@@ -224,23 +328,18 @@ But the main advantage of Anyway::Config is that it can be used [without Rails](
224
328
 
225
329
  ## How to set env vars
226
330
 
227
- Environmental variables for your config should start with your config name, uppercased and underscore-free.
228
-
229
- For example, if your module is called "MyCoolGem" then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
331
+ Environmental variables for your config should start with your config name, uppercased.
230
332
 
231
- Environment variables are type-casted (case-insensitive).
232
-
233
- Examples:
333
+ For example, if your config name is "mycoolgem" then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
234
334
 
335
+ Environment variables are automatically serialized:
235
336
  - `"True"`, `"t"` and `"yes"` to `true`;
236
-
237
337
  - `"False"`, `"f"` and `"no"` to `false`;
238
-
239
338
  - `"nil"` and `"null"` to `nil` (do you really need it?);
240
-
241
339
  - `"123"` to 123 and `"3.14"` to 3.14.
242
340
 
243
341
  *Anyway Config* supports nested (_hashed_) env variables. Just separate keys with double-underscore.
342
+
244
343
  For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
245
344
 
246
345
  Array values are also supported:
@@ -253,13 +352,50 @@ config.ids #=> [1,2,3]
253
352
  If you want to provide a text-like env variable which contains commas then wrap it into quotes:
254
353
 
255
354
  ```ruby
256
- MYCOOLGEM="Nif-Nif, Naf-Naf and Nouf-Nouf"
355
+ MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
257
356
  ```
258
357
 
358
+ ## Test helpers
359
+
360
+ We provide the `with_env` test helper to test code in the context of the specified environment variables values:
361
+
362
+ ```ruby
363
+ describe HerokuConfig, type: :config do
364
+ subject { described_class.new }
365
+
366
+ specify do
367
+ # Ensure that the env vars are set to the specified
368
+ # values within the block and reset to the previous values
369
+ # outside of it.
370
+ with_env(
371
+ "HEROKU_APP_NAME" => "kin-web-staging",
372
+ "HEROKU_APP_ID" => "abc123",
373
+ "HEROKU_DYNO_ID" => "ddyy",
374
+ "HEROKU_RELEASE_VERSION" => "v0",
375
+ "HEROKU_SLUG_COMMIT" => "3e4d5a"
376
+ ) do
377
+ is_expected.to have_attributes(
378
+ app_name: "kin-web-staging",
379
+ app_id: "abc123",
380
+ dyno_id: "ddyy",
381
+ release_version: "v0",
382
+ slug_commit: "3e4d5a"
383
+ )
384
+ end
385
+ end
386
+ end
387
+ ```
388
+
389
+ If you want to delete the env var, pass `nil` as the value.
390
+
391
+ This helper is automatically included to RSpec if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". It's only available for the example with the tag `type: :config` or with the path `spec/configs/...`.
392
+
393
+ You can add it manually by requiring `"anyway/testing/helpers"` and including the `Anyway::Test::Helpers` module (into RSpec configuration or Minitest test class).
394
+
259
395
  ## Contributing
260
396
 
261
- 1. Fork it
262
- 2. Create your feature branch (`git checkout -b my-new-feature`)
263
- 3. Commit your changes (`git commit -am 'Add some feature'`)
264
- 4. Push to the branch (`git push origin my-new-feature`)
265
- 5. Create a new Pull Request
397
+ Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/anyway_config.
398
+
399
+ ## License
400
+
401
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/lib/anyway.rb CHANGED
@@ -2,11 +2,31 @@
2
2
 
3
3
  module Anyway # :nodoc:
4
4
  require "anyway/version"
5
+
5
6
  require "anyway/config"
6
7
  require "anyway/rails/config" if defined?(::Rails::VERSION)
7
8
  require "anyway/env"
8
9
 
9
- def self.env
10
- @env ||= ::Anyway::Env.new
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
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")
11
22
  end
23
+
24
+ class << self
25
+ def env
26
+ @env ||= ::Anyway::Env.new
27
+ end
28
+ end
29
+
30
+ require "anyway/railtie" if defined?(::Rails::VERSION)
31
+ require "anyway/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
12
32
  end
data/lib/anyway/config.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'anyway/ext/jruby' if defined? JRUBY_VERSION
4
- require 'anyway/ext/deep_dup'
5
- require 'anyway/ext/deep_freeze'
6
- require 'anyway/ext/hash'
7
- require 'anyway/ext/string_serialize'
8
- require 'anyway/option_parser_builder'
3
+ require "anyway/optparse_config"
4
+ require "anyway/dynamic_config"
5
+
6
+ require "anyway/ext/jruby" if defined? JRUBY_VERSION
7
+ require "anyway/ext/deep_dup"
8
+ require "anyway/ext/deep_freeze"
9
+ require "anyway/ext/hash"
10
+ require "anyway/ext/string_serialize"
9
11
 
10
12
  module Anyway # :nodoc:
11
13
  if defined? JRUBY_VERSION
@@ -21,108 +23,116 @@ module Anyway # :nodoc:
21
23
  # Provides `attr_config` method to describe
22
24
  # configuration parameters and set defaults
23
25
  class Config
24
- class << self
25
- attr_reader :defaults, :config_attributes, :option_parser_extension
26
+ include OptparseConfig
27
+ include DynamicConfig
26
28
 
29
+ class << self
27
30
  def attr_config(*args, **hargs)
28
- @defaults ||= {}
29
- @config_attributes ||= []
30
-
31
31
  new_defaults = hargs.deep_dup
32
32
  new_defaults.stringify_keys!
33
+
33
34
  defaults.merge! new_defaults
34
35
 
35
36
  new_keys = (args + new_defaults.keys) - config_attributes
36
- @config_attributes += new_keys
37
+ config_attributes.push(*new_keys)
37
38
  attr_accessor(*new_keys)
38
39
  end
39
40
 
40
- def config_name(val = nil)
41
- return (@config_name = val.to_s) unless val.nil?
41
+ def defaults
42
+ return @defaults if instance_variable_defined?(:@defaults)
42
43
 
43
- @config_name = underscore_name unless defined?(@config_name)
44
- @config_name
44
+ @defaults =
45
+ if superclass < Anyway::Config
46
+ superclass.defaults.deep_dup
47
+ else
48
+ {}
49
+ end
45
50
  end
46
51
 
47
- def ignore_options(*args)
48
- args.each do |name|
49
- option_parser_descriptors[name.to_s][:ignore] = true
50
- end
51
- end
52
+ def config_attributes
53
+ return @config_attributes if instance_variable_defined?(:@config_attributes)
52
54
 
53
- def describe_options(**hargs)
54
- hargs.each do |name, desc|
55
- option_parser_descriptors[name.to_s][:desc] = desc
56
- end
55
+ @config_attributes =
56
+ if superclass < Anyway::Config
57
+ superclass.config_attributes.dup
58
+ else
59
+ []
60
+ end
57
61
  end
58
62
 
59
- def flag_options(*args)
60
- args.each do |name|
61
- option_parser_descriptors[name.to_s][:flag] = true
62
- end
63
- end
63
+ def config_name(val = nil)
64
+ return (@explicit_config_name = val.to_s) unless val.nil?
65
+
66
+ return @config_name if instance_variable_defined?(:@config_name)
64
67
 
65
- def extend_options(&block)
66
- @option_parser_extension = block
68
+ @config_name = explicit_config_name || build_config_name
67
69
  end
68
70
 
69
- def option_parser_options
70
- config_attributes.each_with_object({}) do |key, result|
71
- descriptor = option_parser_descriptors[key.to_s]
72
- next if descriptor[:ignore] == true
71
+ def explicit_config_name
72
+ return @explicit_config_name if instance_variable_defined?(:@explicit_config_name)
73
73
 
74
- result[key] = descriptor
75
- end
74
+ @explicit_config_name =
75
+ if superclass.respond_to?(:explicit_config_name)
76
+ superclass.explicit_config_name
77
+ end
78
+ end
79
+
80
+ def explicit_config_name?
81
+ !explicit_config_name.nil?
76
82
  end
77
83
 
78
84
  def env_prefix(val = nil)
79
- return (@env_prefix = val.to_s) unless val.nil?
85
+ return (@env_prefix = val.to_s.upcase) unless val.nil?
80
86
 
81
- @env_prefix
82
- end
87
+ return @env_prefix if instance_variable_defined?(:@env_prefix)
83
88
 
84
- # Load config as Hash by any name
85
- #
86
- # Example:
87
- #
88
- # my_config = Anyway::Config.for(:my_app)
89
- # # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"]
90
- def for(name)
91
- new(name: name, load: false).load_from_sources
89
+ @env_prefix =
90
+ if superclass < Anyway::Config && superclass.explicit_config_name?
91
+ superclass.env_prefix
92
+ else
93
+ config_name.upcase
94
+ end
92
95
  end
93
96
 
94
97
  private
95
98
 
96
- def option_parser_descriptors
97
- @option_parser_descriptors ||= Hash.new { |h, k| h[k] = {} }
98
- end
99
+ def build_config_name
100
+ unless name
101
+ raise "Please, specify config name explicitly for anonymous class " \
102
+ "via `config_name :my_config`"
103
+ end
99
104
 
100
- def underscore_name
101
- return unless name
105
+ # handle two cases:
106
+ # - SomeModule::Config => "some_module"
107
+ # - SomeConfig => "some"
108
+ unless name =~ /^(\w+)(\:\:)?Config$/
109
+ raise "Couldn't infer config name, please, specify it explicitly" \
110
+ "via `config_name :my_config`"
111
+ end
102
112
 
103
- word = name[/^(\w+)/]
104
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
105
- word.downcase!
106
- word
113
+ Regexp.last_match[1].tap(&:downcase!)
107
114
  end
108
115
  end
109
116
 
110
117
  attr_reader :config_name, :env_prefix
111
118
 
112
- # Instantiate config with specified name, loads the data and applies overrides
119
+ # Instantiate config instance.
113
120
  #
114
121
  # Example:
115
122
  #
116
- # my_config = Anyway::Config.new(name: :my_app, load: true, overrides: { some: :value })
123
+ # my_config = Anyway::Config.new()
124
+ #
125
+ # # provide some values explicitly
126
+ # my_config = Anyway::Config.new({some: :value})
117
127
  #
118
- def initialize(name: nil, load: true, overrides: {})
119
- @config_name = name || self.class.config_name
128
+ def initialize(overrides = {})
129
+ @config_name = self.class.config_name
120
130
 
121
131
  raise ArgumentError, "Config name is missing" unless @config_name
122
132
 
123
- @env_prefix = self.class.env_prefix || @config_name
133
+ @env_prefix = self.class.env_prefix
124
134
 
125
- self.load(overrides) if load
135
+ load(overrides)
126
136
  end
127
137
 
128
138
  def reload(overrides = {})
@@ -139,43 +149,49 @@ module Anyway # :nodoc:
139
149
  end
140
150
 
141
151
  def load(overrides = {})
142
- config = load_from_sources((self.class.defaults || {}).deep_dup)
152
+ base_config = (self.class.defaults || {}).deep_dup
153
+
154
+ base_config.deep_merge!(
155
+ load_from_sources(
156
+ name: config_name,
157
+ env_prefix: env_prefix,
158
+ config_path: resolve_config_path(config_name, env_prefix)
159
+ )
160
+ )
161
+
162
+ base_config.merge!(overrides) unless overrides.nil?
143
163
 
144
- config.merge!(overrides) unless overrides.nil?
145
- config.each do |key, val|
164
+ base_config.each do |key, val|
146
165
  set_value(key, val)
147
166
  end
148
167
  end
149
168
 
150
- def load_from_sources(config = {})
151
- # Handle anonymous configs
152
- return config unless config_name
153
-
154
- load_from_file(config)
155
- load_from_env(config)
169
+ def load_from_sources(**options)
170
+ base_config = {}
171
+ each_source(options) do |config|
172
+ base_config.deep_merge!(config) if config
173
+ end
174
+ base_config
156
175
  end
157
176
 
158
- def load_from_file(config)
159
- config.deep_merge!(parse_yml(config_path) || {}) if config_path && File.file?(config_path)
160
- config
177
+ def each_source(options)
178
+ yield load_from_file(options)
179
+ yield load_from_env(options)
161
180
  end
162
181
 
163
- def load_from_env(config)
164
- config.deep_merge!(env_part)
165
- config
166
- end
182
+ def load_from_file(name:, env_prefix:, config_path:, **_options)
183
+ file_config = load_from_yml(config_path)
167
184
 
168
- def option_parser
169
- @option_parser ||= begin
170
- parser = OptionParserBuilder.call(self.class.option_parser_options) do |key, arg|
171
- set_value(key, arg.is_a?(String) ? arg.serialize : arg)
172
- end
173
- self.class.option_parser_extension&.call(parser, self) || parser
185
+ if Anyway::Settings.use_local_files
186
+ local_config_path = config_path.sub(/\.yml/, ".local.yml")
187
+ file_config.deep_merge!(load_from_yml(local_config_path))
174
188
  end
189
+
190
+ file_config
175
191
  end
176
192
 
177
- def parse_options!(options)
178
- option_parser.parse!(options)
193
+ def load_from_env(name:, env_prefix:, **_options)
194
+ Anyway.env.fetch(env_prefix)
179
195
  end
180
196
 
181
197
  def to_h
@@ -184,26 +200,28 @@ module Anyway # :nodoc:
184
200
  end.deep_dup.deep_freeze
185
201
  end
186
202
 
203
+ def resolve_config_path(name, env_prefix)
204
+ Anyway.env.fetch(env_prefix).delete("conf") || default_config_path(name)
205
+ end
206
+
187
207
  private
188
208
 
189
- def env_part
190
- Anyway.env.fetch(env_prefix)
209
+ def set_value(key, val)
210
+ send("#{key}=", val) if respond_to?(key)
191
211
  end
192
212
 
193
- def config_path
194
- env_part.delete('conf') || default_config_path
195
- end
213
+ def load_from_yml(path)
214
+ return {} unless File.file?(path)
196
215
 
197
- def default_config_path
198
- "./config/#{config_name}.yml"
216
+ parse_yml(path)
199
217
  end
200
218
 
201
- def set_value(key, val)
202
- send("#{key}=", val) if respond_to?(key)
219
+ def default_config_path(name)
220
+ "./config/#{name}.yml"
203
221
  end
204
222
 
205
223
  def parse_yml(path)
206
- require 'yaml'
224
+ require "yaml"
207
225
  if defined?(ERB)
208
226
  YAML.safe_load(ERB.new(File.read(path)).result, [], [], true)
209
227
  else