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 @@ [![Gem Version](https://badge.fury.io/rb/anyway_config.svg)](https://rubygems.org/gems/anyway_config) [![Build](https://github.com/palkan/anyway_config/workflows/Build/badge.svg)](https://github.com/palkan/anyway_config/actions) [![JRuby Build](https://github.com/palkan/anyway_config/workflows/JRuby%20Build/badge.svg)](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).