README.md in qonfig-0.18.1 vs README.md in qonfig-0.19.0

- old
+ new

@@ -1,9 +1,10 @@ # 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. Thread-safe. Command-style DSL. Validation layer. Support for **YAML**, **TOML**, **JSON**, **\_\_END\_\_**, **ENV**. +Lazy instantiation. Thread-safe. Command-style DSL. Validation layer. **DOT-NOTATION**! +Support for **YAML**, **TOML**, **JSON**, **\_\_END\_\_**, **ENV**. Extremely simple to define. Extremely simple to use. That's all? **NOT** :) ## Installation ```ruby @@ -45,11 +46,11 @@ - [Interaction](#interaction) - [Iteration over setting keys](#iteration-over-setting-keys) (`#each_setting`, `#deep_each_setting`) - [List of config keys](#list-of-config-keys) (`#keys`, `#root_keys`) - [Config reloading](#config-reloading) (reload config definitions and option values) - [Clear options](#clear-options) (set to `nil`) - - [State freeze](#state-freeze) + - [Frozen state](#frozen-state) (`.freeze_state!`, `#freeze!`, `#frozen?`) - [Settings as Predicates](#settings-as-predicates) - [Setting key existence](#setting-key-existence) (`#key?`/`#option?`/`#setting?`) - [Run arbitrary code with temporary settings](#run-arbitrary-code-with-temporary-settings) (`#with(configs = {}, &arbitrary_code)`) - [Import settings / Export settings](#import-settings--export-settings) - [Import config settings](#import-config-settings) (`as instance methods`) @@ -58,10 +59,11 @@ - [Introduction](#introduction) - [Key search pattern](#key-search-pattern) - [Proc-based validation](#proc-based-validation) - [Method-based validation](#method-based-validation) - [Predefined validations](#predefined-validations) + - [Validation of potential setting values](#validation-of-potential-setting-values) - [Work with files](#work-with-files) - **Setting keys definition** - [Load from YAML file](#load-from-yaml-file) - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs) - [Load from JSON file](#load-from-json-file) @@ -78,10 +80,11 @@ - **Daily work** - [Save to JSON file](#save-to-json-file) (`#save_to_json`) - [Save to YAML file](#save-to-yaml-file) (`#save_to_yaml`) - [Plugins](#plugins) - [toml](#plugins-toml) (support for `TOML` format) + - [pretty_print](#plugins-pretty_print) (beautified/prettified console output) - [Roadmap](#roadmap) --- ## Definition @@ -136,10 +139,12 @@ config.settings.enable_graphql # => false ``` #### access via index-method [] +- without dot-notation: + ```ruby # 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' @@ -156,42 +161,82 @@ config['enable_graphql'] # => false config[:project_id] # => nil config[:enable_graphql] # => false ``` +- with dot-notation: + +```ruby +config.settings['vendor_api.host'] # => 'app.service.com' +config.settings['vendor_api.user'] # => 'test_user' + +config['vendor_api.host'] # => 'app.service.com' +config['vendor_api.user'] # => 'test_user' +``` + #### .dig +- without dot-notation: + ```ruby # 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) ``` +- with dot-notation: + +```ruby +config.dig('vendor_api.host') # => 'app.service.com' # (key exists) +config.dig('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist) +``` + #### .slice +- without dot-notation: + ```ruby # get a hash slice of setting options (and fail when the required key does not exist); config.slice(:vendor_api) # => { 'vendor_api' => { 'host' => 'app_service', 'user' => 'test_user' } } config.slice(:vendor_api, :user) # => { 'user' => 'test_user' } config.slice(:project_api) # => Qonfig::UnknownSettingError # (key does not exist) config.slice(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist) ``` +- with dot-notation: + +```ruby +config.slice('vendor_api.user') # => { 'user' => 'test_user' } +config.slice('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist) +``` + + #### .slice_value +- without dot-notaiton: + ```ruby # get value from the slice of setting options using the given key set # (and fail when the required key does not exist) (works in slice manner); config.slice_value(:vendor_api) # => { 'host' => 'app_service', 'user' => 'test_user' } config.slice_value(:vendor_api, :user) # => 'test_user' config.slice_value(:project_api) # => Qonfig::UnknownSettingError # (key does not exist) config.slice_value(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist) ``` +- with dot-notation: + +```ruby +config.slice_value('vendor_api.user') # => 'test_user' +config.slice_value('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist) +``` + #### .subset +- without dot-notation: + ```ruby # - get a subset (a set of sets) of config settings represented as a hash; # - each key (or key set) represents a requirement of a certain setting key; config.subet(:vendor_api, :enable_graphql) @@ -199,10 +244,17 @@ config.subset(:project_id, [:vendor_api, :host], [:credentials, :user, :login]) # => { 'project_id' => nil, 'host' => 'app.service.com', 'login' => 'D@iVeR' } ``` +- with dot-notation: + +```ruby +config.subset('project_id', 'vendor_api.host', 'credentials.user.login') +# => { 'project_id' => nil, 'host' => 'app.service.com', 'login' => 'D@iVeR' } +``` + --- ### Configuration ```ruby @@ -539,11 +591,11 @@ - [Iteration over setting keys](#iteration-over-setting-keys) (`#each_setting`, `#deep_each_setting`) - [List of config keys](#list-of-config-keys) (`#keys`, `#root_keys`) - [Config reloading](#config-reloading) (reload config definitions and option values) - [Clear options](#clear-options) (set to `nil`) -- [State freeze](#state-freeze) +- [Frozen state](#frozen-state) (`.freeze_state!`, `#freeze!`, `#frozen?`) - [Settings as Predicates](#settings-as-predicates) - [Setting key existence](#setting-key-existence) (`#key?`/`#option?`/`#setting?`) - [Run arbitrary code with temporary settings](#run-arbitrary-code-with-temporary-settings) --- @@ -785,12 +837,14 @@ config.settings.web_api.endpoint # => nil ``` --- -### State freeze +### Frozen state +#### Instance-level + - method signature: `#freeze!`; ```ruby class Config < Qonfig::DataSet setting :logger, Logger.new(STDOUT) @@ -809,10 +863,36 @@ config.reload! # => Qonfig::FrozenSettingsError config.clear! # => Qonfig::FrozenSettingsError ``` +#### Definition-level + +- DSL-method signature: `freeze_state!` +- indicaes that all your config instances should be frozen; +- `freeze_state!` DSL command is not inherited (your child and composed config classes will not have this declaration); + +```ruby +# --- base class --- +class Config < Qonfig::DataSet + setting :test, true + freeze_state! +end + +config = Config.new +config.frozen? # => true +config.settings.test = false # => Qonfig::FrozenSettingsError + +# --- child class --- +class InheritedConfig < Config +end + +inherited_config = InheritedConfig.new +config.frozen? # => false +config.settings.test = false # ok :) +``` + --- ### Settings as Predicates - predicate form: `?` at the end of setting name; @@ -1098,11 +1178,11 @@ service.config_account # => NoMethodError # NOTE: export settings as access methods to config's settings config.export(service, 'web_api.credentials.account', prefix: 'config_') -service.account # => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" } +service.config_account # => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" } ``` --- ## Validation @@ -1110,10 +1190,11 @@ - [Introduction](#introduction) - [Key Search Pattern](#key-search-pattern) - [Proc-based validation](#proc-based-validation) - [Method-based validation](#method-based-validation) - [Predefined validations](#predefined-validations) +- [Validation of potential setting values](#validation-of-potential-setting-values) --- ### Introduction @@ -1131,15 +1212,16 @@ - provides `strict` and `non-strict` behavior (`strict: true` and `strict: false` respectively): - `strict: false` ignores validations for settings with `nil` (allows `nil` value); - `strict: true` does not ignores validations for settings with `nil`; - `strict: false` is used by default; - provides special [key search pattern](#key-search-pattern) for matching setting key names; +- you can validate potential setting values without any assignment ([documentation](#validation-of-potential-setting-values)) - uses the [key search pattern](#key-search-pattern) for definging what the setting key should be validated; - you can define your own custom validation logic and validate dataset instance completely; - validation logic should return **truthy** or **falsy** value; -- supprots two validation techniques (**proc-based** ([doc](#proc-based-validation)) and **dataset-method-based** ([doc](#method-based-validation))): - - **proc-based** (`setting validation`) ([doc](#proc-based-validation)) +- supprots two validation techniques (**proc-based** ([documentation](#proc-based-validation)) and **dataset-method-based** ([documentation](#method-based-validation))): + - **proc-based** (`setting validation`) ([documentation](#proc-based-validation)) ```ruby validate('db.user', strict: true) do |value| value.is_a?(String) end ``` @@ -1147,44 +1229,29 @@ ```ruby validate(strict: false) do settings.user == User[1] end ``` - - **dataset-method-based** (`setting validation`) ([doc](#method-based-validation)) + - **dataset-method-based** (`setting validation`) ([documentation](#method-based-validation)) ```ruby validate 'db.user', by: :check_user, strict: true def check_user(value) value.is_a?(String) end ``` - - **dataset-method-based** (`dataset validation`) ([doc](#method-based-validation)) + - **dataset-method-based** (`dataset validation`) ([documentation](#method-based-validation)) ```ruby validate by: :check_config, strict: false def check_config settings.user == User[1] end ``` -- provides a **set of standard validations** ([doc](#predefined-validations)): +- provides a **set of standard validations** ([documentation](#predefined-validations)): - DSL: `validate 'key.pattern', :predefned_validator`; - supports `strict` behavior; - - realized validators: - - `integer` - - `float` - - `numeric` - - `big_decimal` - - `boolean` - - `string` - - `symbol` - - `text` (string or symbol) - - `array` - - `hash` - - `proc` - - `class` - - `module` - - `not_nil` --- ### Key search pattern @@ -1397,10 +1464,56 @@ config.settings.ignorance = nil # => Qonfig::ValidationError (cant be nil) ``` --- +### Validation of potential setting values + +- (**instance-level**) `#valid_with?(configurations = {})` - check that current config instalce will be valid with passed configurations; +- (**class-level**) `.valid_with?(configurations = {})` - check that potential config instancess will be valid with passed configurations; +- makes no assignments; + +#### #valid_with? (instance-level) + +```ruby +class Config < Qonfig::DataSet + setting :enabled, false + setting :queue do + setting :adapter, 'sidekiq' + end + + validate :enabled, :boolean + validate 'queue.adapter', :string +end + +config = Config.new + +config.valid_with?(enabled: true, queue: { adapter: 'que' }) # => true +config.valid_with?(enabled: 123) # => false (should be a type of boolean) +config.valid_with?(enabled: true, queue: { adapter: Sidekiq }) # => false (queue.adapter should be a type of string) +``` + +#### .valid_with? (class-level) + +```ruby +class Config < Qonfig::DataSet + setting :enabled, false + setting :queue do + setting :adapter, 'sidekiq' + end + + validate :enabled, :boolean + validate 'queue.adapter', :string +end + +Config.valid_with?(enabled: true, queue: { adapter: 'que' }) # => true +Config.valid_with?(enabled: 123) # => false (should be a type of boolean) +Config.valid_with?(enabled: true, queue: { adapter: Sidekiq }) # => false (queue.adapter should be a type of string) +``` + +--- + ## Work with files - **Setting keys definition** - [Load from YAML file](#load-from-yaml-file) - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs) @@ -2517,21 +2630,40 @@ --- ### Plugins +- [toml](#plugins-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`); +- [pretty_print](#plugins-pretty_print) (beautified/prettified console output); + +--- + +#### Usage + +- show available plugins: + ```ruby -# --- show names of registered plugins --- -Qonfig.plugins # => array of strings +Qonfig.plugins # => ["pretty_print", "toml", ..., ...] +``` -# --- load specific plugin --- -Qonfig.plugin(:plugin_name) # or Qonfig.plugin('plugin_name') +- load specific plugin: + +```ruby +Qonfig.plugin(:pretty_print) # or Qonfig.plugin('pretty_print') +# -- or -- +Qonfig.enable(:pretty_print) # or Qonfig.enable('pretty_print') +# -- or -- +Qonfig.load(:pretty_print) # or Qonfig.load('pretty_print') ``` -Provided plugins: +- show load plugins: -- [toml](#plugins-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`) +```ruby +Qonfig.loaded_plugins # => ["pretty_print"] +# -- or -- +Qonfig.enabled_plugins # => ["pretty_print"] +``` --- ### Plugins: toml @@ -2549,25 +2681,92 @@ require 'toml-rb' # 2) enable plugin Qonfig.plugin(:toml) -# 3) use :) +# 3) use toml :) ``` + --- +### Plugins: pretty_print + +- `Qonfig.plugin(:pretty_print)` +- gives you really comfortable and beautiful console output; +- represents all setting keys in dot-notation format; + +#### Example: + +```ruby +class Config < Qonfig::DataSet + setting :api do + setting :domain, 'google.ru' + setting :creds do + setting :account, 'D@iVeR' + setting :password, 'test123' + end + end + + setting :log_requests, true + setting :use_proxy, true +end + +config = Config.new +``` + +- before: + +```shell +=> #<Config:0x00007f9b6c01dab0 + @__lock__= + #<Qonfig::DataSet::Lock:0x00007f9b6c01da60 + @access_lock=#<Thread::Mutex:0x00007f9b6c01da38>, + @arbitary_lock=#<Thread::Mutex:0x00007f9b6c01d9e8>, + @definition_lock=#<Thread::Mutex:0x00007f9b6c01da10>>, + @settings= + #<Qonfig::Settings:0x00007f9b6c01d858 + @__lock__= + #<Qonfig::Settings::Lock:0x00007f9b6c01d808 + @access_lock=#<Thread::Mutex:0x00007f9b6c01d7b8>, + @definition_lock=#<Thread::Mutex:0x00007f9b6c01d7e0>, + @merge_lock=#<Thread::Mutex:0x00007f9b6c01d790>>, + @__mutation_callbacks__= + #<Qonfig::Settings::Callbacks:0x00007f9b6c01d8d0 + @callbacks=[#<Proc:0x00007f9b6c01d8f8@/Users/daiver/Projects/qonfig/lib/qonfig/settings/builder.rb:39>], + @lock=#<Thread::Mutex:0x00007f9b6c01d880>>, + @__options__= + {"api"=> + #<Qonfig::Settings:0x00007f9b6c01d498 +# ... and etc +``` + +- after: + +```shell +=> #<Config:0x00007f9b6c01dab0 + api.domain: "google.ru", + api.creds.account: "D@iVeR", + api.creds.password: "test123", + log_requests: true, + use_proxy: true> + +# -- or -- + +=> #<Config:0x00007f9b6c01dab0 api.domain: "google.ru", api.creds.account: "D@iVeR", api.creds.password: "test123", log_requests: true, use_proxy: true> +``` + +--- + ## Roadmap - **Major**: - distributed configuration server; - cli toolchain; - support for Rails-like secrets; - support for persistent data storages (we want to store configs in multiple databases and files); - Rails reload plugin; - **Minor**: - custom global (and class-level) validators (with a special Validator Definition DSL); - - support for "dot notation" in `#key?`, `#option?`, `#setting?`, `#dig`, `#subset`, `#slice`, `#slice_value`; - - pretty print :))); ## Contributing - Fork it ( https://github.com/0exp/qonfig/fork ) - Create your feature branch (`git checkout -b feature/my-new-feature`)