README.md in qonfig-0.1.0 vs README.md in qonfig-0.2.0

- old
+ new

@@ -1,20 +1,20 @@ -# Qonfig &middot; [![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 &middot; [![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)