README.md in qonfig-0.1.0 vs README.md in qonfig-0.2.0
- old
+ new
@@ -1,20 +1,20 @@
-# Qonfig · [![Gem Version](https://badge.fury.io/rb/qonfig.svg)](https://badge.fury.io/rb/qonfig) [![Build Status](https://travis-ci.org/0exp/qonfig.svg?branch=master)](https://travis-ci.org/0exp/qonfig) [![Coverage Status](https://coveralls.io/repos/github/0exp/qonfig/badge.svg)](https://coveralls.io/github/0exp/qonfig)
+# Qonfig · [![Gem Version](https://badge.fury.io/rb/qonfig.svg)](https://badge.fury.io/rb/qonfig) [![Build Status](https://travis-ci.org/0exp/qonfig.svg?branch=master)](https://travis-ci.org/0exp/qonfig) [![Coverage Status](https://coveralls.io/repos/github/0exp/qonfig/badge.svg?branch=master)](https://coveralls.io/github/0exp/qonfig?branch=master)
Config. Defined as a class. Used as an instance. Support for inheritance and composition.
-Lazy instantiation. Command-style DSL. Extremely simple to define. Extremely simple to use. That's all.
+Lazy instantiation. Thread-safe. Command-style DSL. Extremely simple to define. Extremely simple to use. That's all.
## Installation
```ruby
gem 'qonfig'
```
```shell
$ bundle install
# --- or ---
-gem install 'qonfig'
+$ gem install 'qonfig'
```
```ruby
require 'qonfig'
```
@@ -25,10 +25,17 @@
- [Configuration](#configuration)
- [Inheritance](#inheritance)
- [Composition](#composition)
- [Hash representation](#hash-representation)
- [State freeze](#state-freeze)
+- [Config reloading](#config-reloading) (reload config definitions and option values)
+- [Clear options](#clear-options) (set to nil)
+- [Settings as Predicates](#settings-as-predicates)
+- [Load from YAML file](#load-from-yaml-file)
+- [Load from ENV](#load-from-env)
+- [Load from \_\_END\_\_](#load-from-__end__) (aka `load_from_self`)
+- [Smart Mixin](#smart-mixin) (`Qonfig::Configurable`)
---
### Definition and Access
@@ -38,37 +45,49 @@
setting :project_id
# nested setting
setting :vendor_api do
setting :host, 'app.service.com'
- setting :port, 12345
end
setting :enable_graphql, false
# nested setting reopening
setting :vendor_api do
setting :user, 'test_user'
- setting :password, 'test_password'
end
end
config = Config.new
+# get option value via method
config.settings.project_id # => nil
-config.settings.vendor_api.host # => 'api.service.com'
-config.settings.vendor_api.port # => 12345
+config.settings.vendor_api.host # => 'app.service.com'
config.settings.vendor_api.user # => 'test_user'
-config.settings.vendor_api.password # => 'test_password'
config.settings.enable_graphql # => false
+# get option value via index (with indifferent (string / symbol / mixed) access)
config.settings[:project_id] # => nil
-config.settings[:vendor_api][:host] # => 'api.service.com'
-config.settings[:vendor_api][:port] # => 12345
+config.settings[:vendor_api][:host] # => 'app.service.com'
config.settings[:vendor_api][:user] # => 'test_user'
-config.settings[:vendor_api][:password] # => 'test_password'
config.settings[:enable_graphql] # => false
+
+# get option value via index (with indifferent (string / symbol / mixed) access)
+config.settings['project_id'] # => nil
+config.settings['vendor_api']['host'] # => 'app.service.com'
+config.settings['vendor_api']['user'] # => 'test_user'
+config.settings['enable_graphql'] # => false
+
+# get option value directly via index (with indifferent access)
+config['project_id'] # => nil
+config['enable_graphql'] # => false
+config[:project_id] # => nil
+config[:enable_graphql] # => false
+
+# get option value in Hash#dig manner (and fail when the required key does not exist)
+config.dig(:vendor_api, :host) # => 'app.service.com' # (key exists)
+config.dig(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
```
---
### Configuration
@@ -87,11 +106,11 @@
setting :enable_middlewares, false
end
config = Config.new
-# configure via block
+# configure via proc
config.configure do |conf|
conf.enable_middlewares = true
conf.geo_api.provider = :yandex_maps
conf.testing.engine = :mini_test
end
@@ -103,10 +122,17 @@
# configure via settings object (by setting key)
config.settings[:enable_middlewares] = true
config.settings[:geo_api][:provider] = :rambler_maps
config.settings[:testing][:engine] = :mega_test
+
+# instant configuration via proc
+config = Config.new do |conf|
+ conf.enable_middlewares = false
+ conf.geo_api.provider = :amazon_maps
+ conf.testing.engine = :crypto_test
+end
```
---
### Inheritance
@@ -189,49 +215,476 @@
setting :engine, :native
end
end
setting :adapter do
- setting :default: :memory_sync
+ setting :default, :memory_sync
end
setting :logger, Logger.new(STDOUT)
end
Config.new.to_h
{
- serializers: {
- json: { engine: :ok },
- hash: { engine: :native },
+ "serializers": {
+ "json" => { "engine" => :ok },
+ "hash" => { "engine" => :native },
},
- adapter: { default: :memory_sync },
- logger: #<Logger:0x4b0d79fc>
+ "adapter" => { "default" => :memory_sync },
+ "logger" => #<Logger:0x4b0d79fc>
}
```
---
+### Config reloading
+
+```ruby
+class Config < Qonfig::DataSet
+ setting :db do
+ setting :adapter, 'postgresql'
+ end
+
+ setting :logger, Logger.new(STDOUT)
+end
+
+config = Config.new
+
+config.settings.db.adapter # => 'postgresql'
+config.settings.logger # => #<Logger:0x00007ff9>
+
+config.configure { |conf| conf.logger = nil } # redefine some settings (will be reloaded)
+
+# re-define and append settings
+class Config
+ setting :db do
+ setting :adapter, 'mongoid' # re-define defaults
+ end
+
+ setting :enable_api, false # append new setting
+end
+
+# reload settings
+config.reload!
+
+config.settings.db.adapter # => 'mongoid'
+config.settings.logger # => #<Logger:0x00007ff9> (reloaded from defaults)
+config.settings.enable_api # => false (new setting)
+
+# reload with instant configuration
+config.reload! do |conf|
+ conf.enable_api = true # changed instantly
+end
+
+config.settings.db.adapter # => 'mongoid'
+config.settings.logger = # => #<Logger:0x00007ff9>
+config.settings.enable_api # => true # value from instant change
+```
+
+---
+
+### Clear options
+
+```ruby
+class Config
+ setting :database do
+ setting :user
+ setting :password
+ end
+
+ setting :web_api do
+ setting :endpoint
+ end
+end
+
+config = Config.new do |conf|
+ conf.database.user = '0exp'
+ conf.database.password = 'test123'
+
+ conf.web_api.endpoint = '/api/'
+end
+
+config.settings.database.user # => '0exp'
+config.settings.database.password # => 'test123'
+config.settings.web_api.endpoint # => '/api'
+
+# clear all options
+config.clear!
+
+config.settings.database.user # => nil
+config.settings.database.password # => nil
+config.settings.web_api.endpoint # => nil
+```
+
+---
+
### State freeze
```ruby
class Config < Qonfig::DataSet
setting :logger, Logger.new(STDOUT)
setting :worker, :sidekiq
+ setting :db do
+ setting :adapter, 'postgresql'
+ end
end
config = Config.new
config.freeze!
config.settings.logger = Logger.new(StringIO.new) # => Qonfig::FrozenSettingsError
config.settings.worker = :que # => Qonfig::FrozenSettingsError
+config.settings.db.adapter = 'mongoid' # => Qonfig::FrozenSettingsError
+
+config.reload! # => Qonfig::FrozenSettingsError
```
---
+### Settings as Predicates
+
+- predicate form: `?` at the end of setting name;
+- `nil` and `false` setting values indicates `false`;
+- other setting values indicates `true`;
+- setting roots always returns `true`;
+
+```ruby
+class Config < Qonfig::DataSet
+ setting :database do
+ setting :user
+ setting :host, 'google.com'
+
+ setting :engine do
+ setting :driver, 'postgres'
+ end
+ end
+end
+
+config = Config.new
+
+# predicates
+config.settings.database.user? # => false (nil => false)
+config.settings.database.host? # => true ('google.com' => true)
+config.settings.database.engine.driver? # => true ('postgres' => true)
+
+# setting roots always returns true
+config.settings.database? # => true
+config.settings.database.engine? # => ture
+
+config.configure do |conf|
+ conf.database.user = '0exp'
+ conf.database.host = false
+ conf.database.engine.driver = true
+end
+
+# predicates
+config.settings.database.user? # => true ('0exp' => true)
+config.settings.database.host? # => false (false => false)
+config.settings.database.engine.driver? # => true (true => true)
+```
+
+---
+
+### Load from YAML file
+
+- `:strict` mode (fail behaviour when the required yaml file doesnt exist):
+ - `true` (by default) - causes `Qonfig::FileNotFoundError`;
+ - `false` - do nothing, ignore current command;
+
+```yaml
+<!-- travis.yml -->
+sudo: false
+language: ruby
+rvm:
+ - ruby-head
+ - jruby-head
+```
+
+```yaml
+<!-- project.yml -->
+enable_api: false
+Sidekiq/Scheduler:
+ enable: true
+```
+
+```yaml
+<!-- ruby_data.yml -->
+version: <%= RUBY_VERSION %>
+platform: <%= RUBY_PLATFORM %>
+```
+
+```ruby
+class Config < Qonfig::DataSet
+ setting :ruby do
+ load_from_yaml 'ruby_data.yml'
+ end
+
+ setting :travis do
+ load_from_yaml 'travis.yml'
+ end
+
+ load_from_yaml 'project.yml'
+end
+
+config = Config.new
+
+config.settings.travis.sudo # => false
+config.settings.travis.language # => 'ruby'
+config.settings.travis.rvm # => ['ruby-head', 'jruby-head']
+config.settings.enable_api # => false
+config.settings['Sidekiq/Scheduler']['enable'] #=> true
+config.settings.ruby.version # => '2.5.1'
+config.settings.ruby.platform # => 'x86_64-darwin17'
+```
+
+```ruby
+# --- strict mode ---
+class Config < Qonfig::DataSet
+ setting :nonexistent_yaml do
+ load_from_yaml 'unexistent_file.yml', strict: true # true by default
+ end
+
+ setting :another_key
+end
+
+Config.new # => Qonfig::FileNotFoundError
+
+# --- non-strict mode ---
+class Config < Qonfig::DataSet
+ settings :nonexistent_yaml do
+ load_from_yaml 'unexistent_file.yml', strict: false
+ end
+
+ setting :another_key
+end
+
+Config.new.to_h # => { "nonexistent_yaml" => {}, "another_key" => nil }
+```
+
+---
+
+### Load from ENV
+
+- `:convert_values` (`false` by default):
+ - `'t'`, `'T'`, `'true'`, `'TRUE'` - covnerts to `true`;
+ - `'f'`, `'F'`, `'false'`, `'FALSE'` - covnerts to `false`;
+ - `1`, `23` and etc - converts to `Integer`;
+ - `1.25`, `0.26` and etc - converts to `Float`;
+ - `1, 2, test`, `FALSE,Qonfig` (strings without quotes that contains at least one comma) -
+ converts to `Array` with recursively converted values;
+ - `'"please, test"'`, `"'test, please'"` (quoted strings) - converts to `String` without quotes;
+- `:prefix` - load ENV variables which names starts with a prefix:
+ - `nil` (by default) - empty prefix;
+ - `Regexp` - names that match the regexp pattern;
+ - `String` - names which starts with a passed string;
+- `:trim_prefix` (`false` by default);
+
+```ruby
+# some env variables
+ENV['QONFIG_BOOLEAN'] = 'true'
+ENV['QONFIG_INTEGER'] = '0'
+ENV['QONFIG_STRING'] = 'none'
+ENV['QONFIG_ARRAY'] = '1, 2.5, t, f, TEST'
+ENV['QONFIG_MESSAGE'] = '"Hello, Qonfig!"'
+ENV['RUN_CI'] = '1'
+
+class Config < Qonfig::DataSet
+ # nested
+ setting :qonfig do
+ load_from_env convert_values: true, prefix: 'QONFIG' # or /\Aqonfig.*\z/i
+ end
+
+ setting :trimmed do
+ load_from_env convert_values: true, prefix: 'QONFIG_', trim_prefix: true # trim prefix
+ end
+
+ # on the root
+ load_from_env
+end
+
+config = Config.new
+
+# customized
+config.settings['qonfig']['QONFIG_BOOLEAN'] # => true ('true' => true)
+config.settings['qonfig']['QONFIG_INTEGER'] # => 0 ('0' => 0)
+config.settings['qonfig']['QONFIG_STRING'] # => 'none'
+config.settings['qonfig']['QONFIG_ARRAY'] # => [1, 2.5, true, false, 'TEST']
+config.settings['qonfig']['QONFIG_MESSAGE'] # => 'Hello, Qonfig!'
+config.settings['qonfig']['RUN_CI'] # => Qonfig::UnknownSettingError
+
+# trimmed (and customized)
+config.settings['trimmed']['BOOLEAN'] # => true ('true' => true)
+config.settings['trimmed']['INTEGER'] # => 0 ('0' => 0)
+config.settings['trimmed']['STRING'] # => 'none'
+config.settings['trimmed']['ARRAY'] # => [1, 2.5, true, false, 'TEST']
+config.settings['trimmed']['MESSAGE'] # => 'Hello, Qonfig!'
+config.settings['trimmed']['RUN_CI'] # => Qonfig::UnknownSettingError
+
+# default
+config.settings['QONFIG_BOOLEAN'] # => 'true'
+config.settings['QONFIG_INTEGER'] # => '0'
+config.settings['QONFIG_STRING'] # => 'none'
+config.settings['QONFIG_ARRAY'] # => '1, 2.5, t, f, TEST'
+config.settings['QONFIG_MESSAGE'] # => '"Hello, Qonfig!"'
+config.settings['RUN_CI'] # => '1'
+```
+
+---
+
+### Load from \_\_END\_\_
+
+- aka `load_from_self`
+
+```ruby
+class Config < Qonfig::DataSet
+ load_from_self # on the root
+
+ setting :clowd do
+ load_from_self # nested
+ end
+end
+
+config = Config.new
+
+# on the root
+config.settings.ruby_version # => '2.5.1'
+config.settings.secret_key # => 'top-mega-secret'
+config.settings.api_host # => 'super.puper-google.com'
+config.settings.connection_timeout.seconds # => 10
+config.settings.connection_timeout.enabled # => false
+
+# nested
+config.settings.nested.ruby_version # => '2.5.1'
+config.settings.nested.secret_key # => 'top-mega-secret'
+config.settings.nested.api_host # => 'super.puper-google.com'
+config.settings.nested.connection_timeout.seconds # => 10
+config.settings.nested.connection_timeout.enabled # => false
+
+__END__
+
+ruby_version: <%= RUBY_VERSION %>
+secret_key: top-mega-secret
+api_host: super.puper-google.com
+connection_timeout:
+ seconds: 10
+ enabled: false
+```
+
+---
+
+### Smart Mixin
+
+- class-level:
+ - `.configuration` - settings definitions;
+ - `.configure` - configuration;
+ - `.config` - config object;
+ - settings definitions are inheritable;
+- instance-level:
+ - `#configure` - configuration;
+ - `#config` - config object;
+
+```ruby
+# --- usage ---
+
+class Application
+ # make configurable
+ include Qonfig::Configurable
+
+ configuration do
+ setting :user
+ setting :password
+ end
+end
+
+app = Application.new
+
+# class-level config
+Application.config.settings.user # => nil
+Application.config.settings.password # => nil
+
+# instance-level config
+app.config.settings.user # => nil
+app.config.settings.password # => nil
+
+# class-level configuration
+Application.configure do |conf|
+ conf.user = '0exp'
+ conf.password = 'test123'
+end
+
+# instance-level configuration
+app.configure do |conf|
+ conf.user = 'admin'
+ conf.password = '123test'
+end
+
+# class has own config object
+Application.config.settings.user # => '0exp'
+Application.config.settings.password # => 'test123'
+
+# instance has own config object
+app.config.settings.user # => 'admin'
+app.config.settings.password # => '123test'
+
+# and etc... (all Qonfig-related features)
+```
+
+```ruby
+# --- inheritance ---
+
+class BasicApplication
+ # make configurable
+ include Qonfig::Configurable
+
+ configuration do
+ setting :user
+ setting :pswd
+ end
+
+ configure do |conf|
+ conf.user = 'admin'
+ conf.pswd = 'admin'
+ end
+end
+
+class GeneralApplication < BasicApplication
+ # extend inherited definitions
+ configuration do
+ setting :db do
+ setting :adapter
+ end
+ end
+
+ configure do |conf|
+ conf.user = '0exp' # .user inherited from BasicApplication
+ conf.pswd = '123test' # .pswd inherited from BasicApplication
+ conf.db.adapter = 'pg'
+ end
+end
+
+BasicApplication.config.to_h
+{ 'user' => 'admin', 'pswd' => 'admin' }
+
+GeneralApplication.config.to_h
+{ 'user' => '0exp', 'pswd' => '123test', 'db' => { 'adapter' => 'pg' } }
+
+# and etc... (all Qonfig-related features)
+```
+
+---
+
+## Contributing
+
+- Fork it ( https://github.com/0exp/qonfig/fork )
+- Create your feature branch (`git checkout -b feature/my-new-feature development`)
+- Commit your changes (`git commit -am 'Add some feature'`)
+- Push to the branch (`git push origin feature/my-new-feature`)
+- Create new Pull Request
+
## License
Released under MIT License.
## Authors
-Rustam Ibragimov.
+[Rustam Ibragimov](https://github.com/0exp)