README.md in anyway_config-2.0.0.pre2 vs README.md in anyway_config-2.0.0.rc1
- old
+ new
@@ -2,87 +2,180 @@
[](https://rubygems.org/gems/anyway_config) [](https://github.com/palkan/anyway_config/actions)
[](https://github.com/palkan/anyway_config/actions)
# Anyway Config
-**NOTE:** this readme shows doc for the upcoming 2.0 version (`2.0.0.pre` is available on RubyGems).
+> One configuration to rule all data sources
+
+Anyway Config is a configuration library for Ruby gems and applications.
+
+As a library author, you can benefit from using Anyway Config by providing a better UX for your end-users:
+
+- **Zero-code configuration** — no more boilerplate initializers.
+- **Per-environment and local** settings support out-of-the-box.
+
+For application developers, Anyway Config could be useful to:
+
+- **Keep configuration organized** and use _named configs_ instead of bloated `.env`/`settings.yml`/whatever.
+- **Free code of ENV/credentials/secrets dependency** and use configuration classes instead—your code should not rely on configuration data sources.
+
+**NOTE:** this readme shows documentation for the upcoming 2.0 version (`2.0.0.rc1` is available on RubyGems).
For version 1.x see [1-4-stable branch](https://github.com/palkan/anyway_config/tree/1-4-stable).
-Rails/Ruby plugin/application configuration tool which allows you to load parameters from different sources: YAML, Rails secrets/credentials, environment.
+## Table of contents
-Allows you to easily follow the [twelve-factor application](https://12factor.net/config) principles and adds zero complexity to your development process.
+- [Main concepts](#main-concepts)
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Configuration classes](#configuration-classes)
+ - [Dynamic configuration](#dynamic-configuration)
+ - [Validation & Callbacks](#validation-and-callbacks)
+- [Using with Rails applications](#using-with-rails)
+ - [Data population](#data-population)
+ - [Organizing configs](#organizing-configs)
+ - [Generators](#generators)
+- [Using with Ruby applications](#using-with-ruby)
+- [Environment variables](#environment-variables)
+- [Local configuration](#local-files)
+- [Data loaders](#data-loaders)
+- [Source tracing](#tracing)
+- [Pattern matching](#pattern-matching)
+- [Test helpers](#test-helpers)
+- [OptionParser integration](#optionparser-integration)
-Libraries using Anyway Config:
+## Main concepts
-- [Influxer](https://github.com/palkan/influxer)
+Anyway Config abstractize the configuration layer by introducing **configuration classes** which describe available parameters and their defaults. For [example](https://github.com/palkan/influxer/blob/master/lib/influxer/config.rb):
-- [AnyCable](https://github.com/anycable/anycable)
+```ruby
+module Influxer
+ class Config < Anyway::Config
+ attr_config(
+ host: "localhost",
+ username: "root",
+ password: "root"
+ )
+ end
+end
+```
-- [Sniffer](https://github.com/aderyabin/sniffer)
+Using Ruby classes to represent configuration allows you to add helper methods and computed parameters easily, makes the configuration **testable**.
-- [Blood Contracts](https://github.com/sclinede/blood_contracts)
+The `anyway_config` gem takes care of loading parameters from **different sources** (YAML, credentials/secrets, environment variables, etc.). Internally, we use a _pipeline pattern_ and provide the [Loaders API](#data-loaders) to manage and [extend](#custom-loaders) its functionality.
+Check out the libraries using Anyway Config for more examples:
+
+- [Influxer](https://github.com/palkan/influxer)
+- [AnyCable](https://github.com/anycable/anycable)
+- [Sniffer](https://github.com/aderyabin/sniffer)
+- [Blood Contracts](https://github.com/sclinede/blood_contracts)
- [and others](https://github.com/palkan/anyway_config/network/dependents).
## Installation
Adding to a gem:
```ruby
# my-cool-gem.gemspec
Gem::Specification.new do |spec|
# ...
- spec.add_dependency "anyway_config", "2.0.0.pre"
+ spec.add_dependency "anyway_config", "2.0.0.rc1"
# ...
end
```
Or adding to your project:
```ruby
# Gemfile
-gem "anyway_config", "2.0.0.pre"
+gem "anyway_config", "2.0.0.rc1"
```
-## Supported Ruby versions
+### Supported Ruby versions
- Ruby (MRI) >= 2.5.0
+- JRuby >= 9.2.9
-- JRuby >= 9.2.7
-
## Usage
-### Pre-defined configuration
+### Configuration classes
-Create configuration class:
+Using configuration classes allows you to make configuration data a bit more than a bag of values:
+you can define a schema for your configuration, provide defaults, add validations and additional helper methods.
+Anyway Config provides a base class to inherit from with a few DSL methods:
+
```ruby
-require "anyway"
+require "anyway_config"
module MyCoolGem
class Config < Anyway::Config
attr_config user: "root", password: "root", host: "localhost"
end
end
```
-`attr_config` creates accessors and default values. If you don't need default values just write:
+Here `attr_config` creates accessors and populates the default values. If you don't need default values you can write:
```ruby
attr_config :user, :password, host: "localhost", options: {}
```
-Then create an instance of the config class and use it:
+**NOTE**: it's safe to use non-primitive default values (like Hashes or Arrays) without worrying about their mutation: the values would be deeply duplicated for each config instance.
+Then, create an instance of the config class and use it:
+
```ruby
-module MyCoolGem
- def self.config
- @config ||= Config.new
+MyCoolGem::Config.new.user #=> "root"
+```
+
+**Bonus:**: if you define attributes with boolean default values (`false` or `true`), Anyway Config would automatically add a corresponding predicate method. For example:
+
+```ruby
+attr_config :user, :password, debug: false
+
+MyCoolGem::Config.new.debug? #=> false
+MyCoolGem::Config.new(debug: true).debug? #=> true
+```
+
+**NOTE**: since v2.0 accessors created by `attr_config` are not `attr_accessor`, i.e. they do not populate instance variables. If you used instance variables before to override readers, you must switch to using `super` or `values` store:
+
+```ruby
+class MyConfig < Anyway::Config
+ attr_config :host, :port, :url, :meta
+
+ # override writer to handle type coercion
+ def meta=(val)
+ super JSON.parse(val)
end
+
+ # or override reader to handle missing values
+ def url
+ super || (self.url = "#{host}:#{port}")
+ end
+
+ # untill v2.1, it will still be possible to read instance variables,
+ # i.e. the following code would also work
+ def url
+ @url ||= "#{host}:#{port}"
+ end
end
+```
-MyCoolGem.config.user #=> "root"
+We recommend to add a feature check and support both v1.x and v2.0 in gems for the time being:
+
+```ruby
+# Check for the class method added in 2.0, e.g., `.on_load`
+if respond_to?(:on_load)
+ def url
+ super || (self.url = "#{host}:#{port}")
+ end
+else
+ def url
+ @url ||= "#{host}:#{port}"
+ end
+end
```
#### Config name
Anyway Config relies on the notion of _config name_ to populate data.
@@ -90,13 +183,13 @@
By default, Anyway Config uses the config class name to infer the config name using the following rules:
- if the class name has a form of `<Module>::Config` then use the module name (`SomeModule::Config => "somemodule"`)
- if the class name has a form of `<Something>Config` then use the class name prefix (`SomeConfig => "some"`)
-**NOTE:** in both cases the config name is a **downcased** module/class prefix, not underscored.
+**NOTE:** in both cases, the config name is a **downcased** module/class prefix, not underscored.
-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):
+You can also specify the config name explicitly (it's required in cases when your class name doesn't match any of the patterns above):
```ruby
module MyCoolGem
class Config < Anyway::Config
config_name :cool
@@ -120,45 +213,117 @@
attr_config user: "root", password: "root", host: "localhost", options: {}
end
end
```
-#### Provide explicit values
+#### Explicit values
Sometimes it's useful to set some parameters explicitly during config initialization.
You can do that by passing a Hash into `.new` method:
```ruby
config = MyCoolGem::Config.new(
user: "john",
password: "rubyisnotdead"
)
-# The value would not be overriden from other sources (such as YML file, env)
+# The value would not be overridden from other sources (such as YML file, env)
config.user == "john"
```
+#### Reload configuration
+
+There are `#clear` and `#reload` methods that do exactly what they state.
+
+**NOTE**: `#reload` also accepts an optional Hash for [explicit values](#explicit-values).
+
### Dynamic configuration
-You can also create configuration objects without pre-defined schema (just like `Rails.application.config_for` but more [powerful](#railsapplicationconfig_for-vs-anywayconfigfor)):
+You can also fetch configuration without pre-defined schema:
```ruby
-# load data from config/my_app.yml, secrets.my_app (if using Rails), ENV["MY_APP_*"]
-# MY_APP_VALUE=42
+# load data from config/my_app.yml,
+# credentials.my_app, secrets.my_app (if using Rails), ENV["MY_APP_*"]
+#
+# Given MY_APP_VALUE=42
config = Anyway::Config.for(:my_app)
config["value"] #=> 42
# you can specify the config file path or env prefix
config = Anyway::Config.for(:my_app, config_path: "my_config.yml", env_prefix: "MYAPP")
```
-### Using with Rails
+This feature is similar to `Rails.application.config_for` but more powerful:
+| Feature | Rails | Anyway Config |
+| ------------- |-------------:| -----:|
+| Load data from `config/app.yml` | ✅ | ✅ |
+| Load data from `secrets` | ❌ | ✅ |
+| Load data from `credentials` | ❌ | ✅ |
+| Load data from environment | ❌ | ✅ |
+| Load data from [custom sources](#data-loaders) | ❌ | ✅ |
+| Local config files | ❌ | ✅ |
+| [Source tracing](#tracing) | ❌ | ✅ |
+| Return Hash with indifferent access | ❌ | ✅ |
+| Support ERB\* within `config/app.yml` | ✅ | ✅ |
+| Raise if file doesn't exist | ✅ | ❌ |
+| Works without Rails | 😀 | ✅ |
+
+\* Make sure that ERB is loaded
+
+### Validation and callbacks
+
+Anyway Config provides basic ways of ensuring that the configuration is valid.
+
+There is a built-in `required` class method to define the list of parameters that must be present in the
+configuration after loading (where present means non-`nil` and non-empty for strings):
+
+```ruby
+class MyConfig < Anyway::Config
+ attr_config :api_key, :api_secret, :debug
+
+ required :api_key, :api_secret
+end
+
+MyConfig.new(api_secret: "") #=> raises Anyway::Config::ValidationError
+```
+
+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:
+
+```ruby
+class MyConfig < Anyway::Config
+ attr_config :api_key, :api_secret, :mode
+
+ # on_load macro accepts symbol method names
+ on_load :ensure_mode_is_valid
+
+ # or block
+ on_load do
+ # the block is evaluated in the context of the config
+ raise_validation_error("API key and/or secret could be blank") if
+ api_key.blank? || api_secret.blank?
+ end
+
+ def ensure_mode_is_valid
+ unless %w[production test].include?(mode)
+ raise_validation_error "Unknown mode; #{mode}"
+ end
+ end
+end
+```
+
+## Using with Rails
+
**NOTE:** version 2.x supports Rails >= 5.0; for Rails 4.x use version 1.x of the gem.
-Your config will be filled up with values from the following sources (ordered by priority from low to high):
+We recommend going through [Data population](#data-population) and [Organizing configs](#organizing-configs) sections first,
+and then use [Rails generators](#generators) to make your application Anyway Config-ready.
+### Data population
+
+Your config is filled up with values from the following sources (ordered by priority from low to high):
+
- `RAILS_ROOT/config/my_cool_gem.yml` (for the current `RAILS_ENV`, supports `ERB`):
```yml
test:
host: localhost
@@ -178,30 +343,34 @@
development:
my_cool_gem:
port: 4444
```
-- `Rails.application.credentials` (if supported):
+- `Rails.application.credentials.my_cool_gem` (if supported):
```yml
my_cool_gem:
host: secret.host
```
**NOTE:** You can backport Rails 6 per-environment credentials to Rails 5.2 app using [this patch](https://gist.github.com/palkan/e27e4885535ff25753aefce45378e0cb).
- `ENV['MYCOOLGEM_*']`.
-#### `app/configs`
+See [environment variables](#environment-variables).
-You can store application-level config classes in `app/configs` folder.
+### Organizing configs
-Anyway Config automatically adds this folder to Rails autoloading system to make it possible to
-autoload configs even during the configuration phase.
+You can store application-level config classes in `app/configs` folder just like any other Rails entities.
-Consider an example: setting the Action Mailer host name for Heroku review apps.
+However, in that case you won't be able to use them during the application initialization (i.e., in `config/**/*.rb` files).
+Since that's a pretty common scenario, we provide a way to do that via a custom autoloader for `config/configs` folder.
+That means, that you can put your configuration classes into `config/configs` folder, use them anywhere in your code without explicitly requiring them.
+
+Consider an example: setting the Action Mailer hostname for Heroku review apps.
+
We have the following config to fetch the Heroku provided [metadata](https://devcenter.heroku.com/articles/dyno-metadata):
```ruby
# This data is provided by Heroku Dyno Metadadata add-on.
class HerokuConfig < Anyway::Config
@@ -219,148 +388,242 @@
```ruby
config.action_mailer.default_url_options = {host: HerokuConfig.new.hostname}
```
-### Using with Ruby
+You can configure the configs folder path:
-When you're using Anyway Config in non-Rails environment, we're looking for a YAML config file
-at `./config/<config-name>.yml`.
+```ruby
+# The path must be relative to Rails root
+config.anyway_config.autoload_static_config_path = "path/to/configs"
+```
-You can override this setting through special environment variable – 'MYCOOLGEM_CONF' – containing the path to the YAML file.
+**NOTE:** Configs loaded from the `autoload_static_config_path` are **not reloaded in development**. We call them _static_. So, it makes sense to keep only configs necessary for initialization in this folder. Other configs, _dynamic_, could be stored in `app/configs`.
+Or you can store everything in `app/configs` by setting `config.anyway_config.autoload_static_config_path = "app/configs"`.
-**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:
+### Generators
+Anyway Config provides Rails generators to create new config classes:
+
+- `rails g anyway:install`—creates an `ApplicationConfig` class (the base class for all config classes) and updates `.gitignore`
+
+You can specify the static configs path via the `--configs-path` option:
+
+```sh
+rails g anyway:install --configs-path=config/settings
+
+# or to keep everything in app/configs
+rails g anyway:install --configs-path=app/configs
+```
+
+- `rails g anyway:config <name> param1 param2 ...`—creates a named configuration class and optionally the corresponding YAML file; creates `application_config.rb` is missing.
+
+The generator command for the Heroku example above would be:
+
+```sh
+$ rails g anyway:config heroku app_id app_name dyno_id release_version slug_commit
+
+ generate anyway:install
+ rails generate anyway:install
+ create config/configs/application_config.rb
+ append .gitignore
+ create config/configs/heroku_config.rb
+Would you like to generate a heroku.yml file? (Y/n) n
+```
+
+You can also specify the `--app` option to put the newly created class into `app/configs` folder.
+Alternatively, you can call `rails g anyway:app_config name param1 param2 ...`.
+
+## Using with Ruby
+
+The default data loading mechanism for non-Rails applications is the following (ordered by priority from low to high):
+
+- `./config/<config-name>.yml` (`ERB` is supported if `erb` is loaded)
+
+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:
+
```yml
host: localhost
port: 3000
```
-Environmental variables work the same way as with Rails.
+**NOTE:** you can override the default YML lookup path by setting `MYCOOLGEM_CONF` env variable.
-### Local files
+- `ENV['MYCOOLGEM_*']`.
-It's useful to have personal, user-specific configuration in development, which extends the project-wide one.
+See [environment variables](#environment-variables).
+## Environment variables
+
+Environmental variables for your config should start with your config name, upper-cased.
+
+For example, if your config name is "mycoolgem", then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
+
+Environment variables are automatically type cast:
+
+- `"True"`, `"t"` and `"yes"` to `true`;
+- `"False"`, `"f"` and `"no"` to `false`;
+- `"nil"` and `"null"` to `nil` (do you really need it?);
+- `"123"` to 123 and `"3.14"` to 3.14.
+
+*Anyway Config* supports nested (_hashed_) env variables—just separate keys with double-underscore.
+
+For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
+
+Array values are also supported:
+
+```ruby
+# Suppose ENV["MYCOOLGEM_IDS"] = '1,2,3'
+config.ids #=> [1,2,3]
+```
+
+If you want to provide a text-like env variable which contains commas then wrap it into quotes:
+
+```ruby
+MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
+```
+
+## Local files
+
+It's useful to have a personal, user-specific configuration in development, which extends the project-wide one.
+
We support this by looking at _local_ files when loading the configuration data:
- `<config_name>.local.yml` files (next to\* the _global_ `<config_name>.yml`)
- `config/credentials/local.yml.enc` (for Rails >= 6, generate it via `rails credentials:edit --environment local`).
-\* If the YAML config path is not default (i.e. set via `<CONFIG_NAME>_CONF`), we lookup the local
+\* If the YAML config path is not a default one (i.e., set via `<CONFIG_NAME>_CONF`), we look up the local
config at this location, too.
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"`).
**NOTE:** in Rails apps you can use `Rails.application.configuration.anyway_config.use_local_files`.
Don't forget to add `*.local.yml` (and `config/credentials/local.*`) to your `.gitignore`.
-**NOTE:** local YAML configs for Rails app must be environment-free (i.e. you shouldn't have top-level `development:` key).
+**NOTE:** local YAML configs for a Rails app must be environment-free (i.e., you shouldn't have top-level `development:` key).
-### Reload configuration
+## Data loaders
-There are `#clear` and `#reload` methods which do exactly what they state.
+You can provide your own data loaders or change the existing ones using the Loaders API (which is very similar to Rack middleware builder):
-Note: `#reload` also accepts `overrides` key to provide explicit values (see above).
+```ruby
+# remove env loader => do not load params from ENV
+Anyway.loaders.delete :env
-### OptionParser integration
+# add custom loader before :env (it's better to keep the ENV loader the last one)
+Anyway.loaders.insert_before :env, :my_loader, MyLoader
+```
-It's possible to use config as option parser (e.g. for CLI apps/libraries). It uses
-[`optparse`](https://ruby-doc.org/stdlib-2.5.1/libdoc/optparse/rdoc/OptionParser.html) under the hood.
+Loader is a _callable_ Ruby object (module/class responding to `.call` or lambda/proc), which `call` method
+accepts the following keyword arguments:
-Example usage:
-
```ruby
-class MyConfig < Anyway::Config
- attr_config :host, :log_level, :concurrency, :debug, server_args: {}
+def call(
+ name:, # config name
+ env_prefix:, # prefix for env vars if any
+ config_path:, # path to YML config
+ local: # true|false, whether to load local configuration
+)
+ #=> must return Hash with configuration data
+end
+```
- # specify which options shouldn't be handled by option parser
- ignore_options :server_args
+You can use `Anyway::Loaders::Base` as a base class for your loader and define a `#call` method.
+For example, the [Chamber](https://github.com/thekompanee/chamber) loader could be written as follows:
- # provide description for options
- describe_options(
- concurrency: "number of threads to use"
- )
+```ruby
+class ChamberConfigLoader < Anyway::Loaders::Base
+ def call(name:, **_opts)
+ Chamber.env.to_h[name] || {}
+ end
+end
+```
- # mark some options as flag
- flag_options :debug
+In order to support [source tracing](#tracing), you need to wrap the resulting Hash via the `#trace!` method with metadata:
- # extend an option parser object (i.e. add banner or version/help handlers)
- extend_options do |parser, config|
- parser.banner = "mycli [options]"
-
- parser.on("--server-args VALUE") do |value|
- config.server_args = JSON.parse(value)
- end
-
- parser.on_tail "-h", "--help" do
- puts parser
- end
+```ruby
+def call(name:, **_opts)
+ trace!(source: :chamber) do
+ Chamber.env.to_h[name] || {}
end
end
+```
-config = MyConfig.new
+## Tracing
-config.parse_options!(%w[--host localhost --port 3333 --log-level debug])
+Since Anyway Config loads data from multiple source, it could be useful to know where a particular value came from.
-config.host # => "localhost"
-config.port # => 3333
-config.log_level # => "debug"
+Each `Anyway::Config` instance contains _tracing information_ which you can access via `#to_source_trace` method:
-# Get the instance of OptionParser
-config.option_parser
-```
+```ruby
+conf = ExampleConfig.new
+conf.to_source_trace
-## `Rails.application.config_for` vs `Anyway::Config.for`
+# returns the following hash
+{
+ "host" => {value: "test.host", source: {type: :yml, path: "config/example.yml"}},
+ "user" => {
+ "name" => {value: "john", source: {type: :env, key: "EXAMPLE_USER__NAME"}},
+ "password" => {value: "root", source: {type: :credentials, store: "config/credentials/production.enc.yml"}}
+ },
+ "port" => {value: 9292, source: {type: :defaults}}
+}
-Rails 4.2 introduced new feature: `Rails.application.config_for`. It looks very similar to
-`Anyway::Config.for`, but there are some differences:
+# if you change the value manually in your code,
+# that would be reflected in the trace
-| Feature | Rails | Anyway Config |
-| ------------- |-------------:| -----:|
-| load data from `config/app.yml` | yes | yes |
-| load data from `secrets` | no | yes |
-| load data from `credentials` | no | yes |
-| load data from environment | no | yes |
-| local config files | no | yes |
-| return Hash with indifferent access | no | yes |
-| support ERB within `config/app.yml` | yes | yes* |
-| raise errors if file doesn't exist | yes | no |
+conf.host = "anyway.host"
+conf.to_source_trace["host"]
+#=> {type: :user, called_from: "/path/to/caller.rb:15"}
+```
-<sub><sup>*</sup>make sure that ERB is loaded</sub>
+You can disable tracing functionality by setting `Anyway::Settings.tracing_enabled = false` or `config.anyway_config.tracing_enabled = false` in Rails.
-But the main advantage of Anyway::Config is that it can be used [without Rails](#using-with-ruby)!)
+### Pretty print
-## How to set env vars
+You can use `pp` to print a formatted information about the config including the sources trace.
-Environmental variables for your config should start with your config name, upper-cased.
+Example:
-For example, if your config name is "mycoolgem" then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
+```ruby
+pp CoolConfig.new
-Environment variables are automatically serialized:
+# #<CoolConfig
+# config_name="cool"
+# env_prefix="COOL"
+# values:
+# port => 3334 (type=load),
+# host => "test.host" (type=yml path=./config/cool.yml),
+# user =>
+# name => "john" (type=env key=COOL_USER__NAME),
+# password => "root" (type=yml path=./config/cool.yml)>
+```
-- `"True"`, `"t"` and `"yes"` to `true`;
-- `"False"`, `"f"` and `"no"` to `false`;
-- `"nil"` and `"null"` to `nil` (do you really need it?);
-- `"123"` to 123 and `"3.14"` to 3.14.
+## Pattern matching
-*Anyway Config* supports nested (_hashed_) env variables. Just separate keys with double-underscore.
+You can use config instances in Ruby 2.7+ pattern matching:
-For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
-
-Array values are also supported:
-
```ruby
-# Suppose ENV["MYCOOLGEM_IDS"] = '1,2,3'
-config.ids #=> [1,2,3]
+case AWSConfig.new
+in bucket:, region: "eu-west-1"
+ setup_eu_storage(bucket)
+in bucket:, region: "us-east-1"
+ setup_us_storage(bucket)
+end
```
-If you want to provide a text-like env variable which contains commas then wrap it into quotes:
+If the attribute wasn't populated, the key won't be returned for pattern matching, i.e. you can do something line:
```ruby
-MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
+aws_configured =
+ case AWSConfig.new
+ in access_key_id:, secret_access_key:
+ true
+ else
+ false
+ end
```
## Test helpers
We provide the `with_env` test helper to test code in the context of the specified environment variables values:
@@ -395,9 +658,71 @@
If you want to delete the env var, pass `nil` as the value.
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/...`.
You can add it manually by requiring `"anyway/testing/helpers"` and including the `Anyway::Test::Helpers` module (into RSpec configuration or Minitest test class).
+
+## OptionParser integration
+
+It's possible to use config as option parser (e.g., for CLI apps/libraries). It uses
+[`optparse`](https://ruby-doc.org/stdlib-2.5.1/libdoc/optparse/rdoc/OptionParser.html) under the hood.
+
+Example usage:
+
+```ruby
+class MyConfig < Anyway::Config
+ attr_config :host, :log_level, :concurrency, :debug, server_args: {}
+
+ # specify which options shouldn't be handled by option parser
+ ignore_options :server_args
+
+ # provide description for options
+ describe_options(
+ concurrency: "number of threads to use"
+ )
+
+ # mark some options as flag
+ flag_options :debug
+
+ # extend an option parser object (i.e. add banner or version/help handlers)
+ extend_options do |parser, config|
+ parser.banner = "mycli [options]"
+
+ parser.on("--server-args VALUE") do |value|
+ config.server_args = JSON.parse(value)
+ end
+
+ parser.on_tail "-h", "--help" do
+ puts parser
+ end
+ end
+end
+
+config = MyConfig.new
+
+config.parse_options!(%w[--host localhost --port 3333 --log-level debug])
+
+config.host # => "localhost"
+config.port # => 3333
+config.log_level # => "debug"
+
+# Get the instance of OptionParser
+config.option_parser
+```
+
+**NOTE:** values are automatically type cast using the same rules as for [environment variables](#environment-variables).
+If you want to specify the type explicitly, you can do that using `describe_options`:
+
+```ruby
+describe_options(
+ # In this case, you should specify a hash with `type`
+ # and (optionally) `desc` keys
+ concurrency: {
+ desc: "number of threads to use",
+ type: String
+ }
+)
+```
## Contributing
Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/anyway_config](https://github.com/palkan/anyway_config).