README.md in qonfig-0.18.1 vs README.md in qonfig-0.19.0
- old
+ new
@@ -1,9 +1,10 @@
# 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. 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`)