qonfig 0.0.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -2
  3. data/.jrubyrc +1 -0
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +15 -0
  6. data/.travis.yml +43 -4
  7. data/CHANGELOG.md +121 -0
  8. data/Gemfile +4 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +1060 -19
  11. data/Rakefile +18 -4
  12. data/bin/console +5 -11
  13. data/bin/rspec +55 -0
  14. data/bin/setup +1 -0
  15. data/gemfiles/with_external_deps.gemfile +8 -0
  16. data/gemfiles/without_external_deps.gemfile +5 -0
  17. data/lib/qonfig.rb +22 -2
  18. data/lib/qonfig/command_set.rb +67 -0
  19. data/lib/qonfig/commands.rb +15 -0
  20. data/lib/qonfig/commands/add_nested_option.rb +45 -0
  21. data/lib/qonfig/commands/add_option.rb +41 -0
  22. data/lib/qonfig/commands/base.rb +12 -0
  23. data/lib/qonfig/commands/compose.rb +37 -0
  24. data/lib/qonfig/commands/expose_yaml.rb +159 -0
  25. data/lib/qonfig/commands/load_from_env.rb +95 -0
  26. data/lib/qonfig/commands/load_from_env/value_converter.rb +84 -0
  27. data/lib/qonfig/commands/load_from_json.rb +56 -0
  28. data/lib/qonfig/commands/load_from_self.rb +73 -0
  29. data/lib/qonfig/commands/load_from_yaml.rb +58 -0
  30. data/lib/qonfig/configurable.rb +116 -0
  31. data/lib/qonfig/data_set.rb +213 -0
  32. data/lib/qonfig/data_set/class_builder.rb +27 -0
  33. data/lib/qonfig/data_set/validator.rb +7 -0
  34. data/lib/qonfig/dsl.rb +122 -0
  35. data/lib/qonfig/errors.rb +111 -0
  36. data/lib/qonfig/loaders.rb +9 -0
  37. data/lib/qonfig/loaders/basic.rb +38 -0
  38. data/lib/qonfig/loaders/json.rb +24 -0
  39. data/lib/qonfig/loaders/yaml.rb +24 -0
  40. data/lib/qonfig/plugins.rb +65 -0
  41. data/lib/qonfig/plugins/abstract.rb +13 -0
  42. data/lib/qonfig/plugins/access_mixin.rb +38 -0
  43. data/lib/qonfig/plugins/registry.rb +125 -0
  44. data/lib/qonfig/plugins/toml.rb +26 -0
  45. data/lib/qonfig/plugins/toml/commands/expose_toml.rb +146 -0
  46. data/lib/qonfig/plugins/toml/commands/load_from_toml.rb +49 -0
  47. data/lib/qonfig/plugins/toml/data_set.rb +19 -0
  48. data/lib/qonfig/plugins/toml/dsl.rb +27 -0
  49. data/lib/qonfig/plugins/toml/loaders/toml.rb +24 -0
  50. data/lib/qonfig/plugins/toml/tomlrb_fixes.rb +92 -0
  51. data/lib/qonfig/plugins/toml/uploaders/toml.rb +25 -0
  52. data/lib/qonfig/settings.rb +457 -0
  53. data/lib/qonfig/settings/builder.rb +18 -0
  54. data/lib/qonfig/settings/key_guard.rb +71 -0
  55. data/lib/qonfig/settings/lock.rb +60 -0
  56. data/lib/qonfig/uploaders.rb +10 -0
  57. data/lib/qonfig/uploaders/base.rb +18 -0
  58. data/lib/qonfig/uploaders/file.rb +55 -0
  59. data/lib/qonfig/uploaders/json.rb +35 -0
  60. data/lib/qonfig/uploaders/yaml.rb +93 -0
  61. data/lib/qonfig/version.rb +7 -1
  62. data/qonfig.gemspec +29 -17
  63. metadata +122 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 640c084d4fd0dc167cfb9b52d12ae4d2f70382ec4d2f576703fc8be3a748fc08
4
- data.tar.gz: a36a0d612d46c542e9e20609a57c6fcf9ea244fce30fdbf295af8bc130ac9223
3
+ metadata.gz: 62b74a92c4c74b79f222e029726296a63b51ba3a57886a00911a34ffbcbced25
4
+ data.tar.gz: 33fa91631b6554dec3547c9c31faddf805bc4a56becbe7042e1942c19c493f16
5
5
  SHA512:
6
- metadata.gz: 3922f86f65e5d9486074c9ca425821a1d6d6ccb8e9655089c20e95696ece61a99a00e18aa19078b6fa71f069acad5558dcf3a622944c2b72348996774a8e79fc
7
- data.tar.gz: f71c819eafab31c35d1faa5cfeaa6a28c9fcee4898d2dc492385391c04e4c50c7376ca2f5a45560d86b25fa98bfab486122f970d9d86c70c77b318a98ab79ccb
6
+ metadata.gz: 152acb89e834fccaaed1b4bfae379f8263d795b2e4e7b7913f6a2b98fd1bfebf984dec1c82a3568ddc08a3e16fdec2be7783f1f583ef09a3dc6b1386f9afb18f
7
+ data.tar.gz: c468d5a5c289dc8b4b7b337e62314dbbf9d388b5dd6df1248a14e957a97be9937d7f24c47aa8634ffe415dbd092ed4f3c5b447036770b171fb6d87454eceb4e8
data/.gitignore CHANGED
@@ -6,6 +6,10 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
-
10
- # rspec failure tracking
11
9
  .rspec_status
10
+ Gemfile.lock
11
+ /.idea
12
+ .ruby-version
13
+ /.vscode/
14
+ /spec/artifacts/
15
+ /gemfiles/*.gemfile.lock
@@ -0,0 +1 @@
1
+ debug.fullTrace=true
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- --format documentation
2
1
  --color
2
+ --format progress
3
3
  --require spec_helper
@@ -0,0 +1,15 @@
1
+ inherit_gem:
2
+ armitage-rubocop:
3
+ - lib/rubocop.general.yml
4
+ - lib/rubocop.rspec.yml
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.6.3
8
+ Include:
9
+ - lib/**/*.rb
10
+ - spec/**/*.rb
11
+ - Gemfile
12
+ - Rakefile
13
+ - qonfig.gemspec
14
+ - bin/console
15
+ - bin/rspec
@@ -1,5 +1,44 @@
1
- sudo: false
2
1
  language: ruby
3
- rvm:
4
- - 2.5.1
5
- before_install: gem install bundler -v 1.16.1
2
+ matrix:
3
+ fast_finish: true
4
+ include:
5
+ - rvm: 2.3.8
6
+ gemfile: gemfiles/with_external_deps.gemfile
7
+ env: TEST_PLUGINS=true
8
+ - rvm: 2.4.6
9
+ gemfile: gemfiles/with_external_deps.gemfile
10
+ env: TEST_PLUGINS=true
11
+ - rvm: 2.5.5
12
+ gemfile: gemfiles/with_external_deps.gemfile
13
+ env: TEST_PLUGINS=true
14
+ - rvm: 2.6.3
15
+ gemfile: gemfiles/with_external_deps.gemfile
16
+ env: TEST_PLUGINS=true
17
+ - rvm: ruby-head
18
+ gemfile: gemfiles/with_external_deps.gemfile
19
+ env: TEST_PLUGINS=true
20
+ - rvm: jruby-head
21
+ gemfile: gemfiles/with_external_deps.gemfile
22
+ env: TEST_PLUGINS=true
23
+ - rvm: 2.3.8
24
+ gemfile: gemfiles/without_external_deps.gemfile
25
+ - rvm: 2.4.6
26
+ gemfile: gemfiles/without_external_deps.gemfile
27
+ - rvm: 2.5.5
28
+ gemfile: gemfiles/without_external_deps.gemfile
29
+ - rvm: 2.6.3
30
+ gemfile: gemfiles/without_external_deps.gemfile
31
+ - rvm: ruby-head
32
+ gemfile: gemfiles/without_external_deps.gemfile
33
+ - rvm: jruby-head
34
+ gemfile: gemfiles/without_external_deps.gemfile
35
+ allow_failures:
36
+ - rvm: ruby-head
37
+ - rvm: jruby-head
38
+ sudo: false
39
+ cache: bundler
40
+ before_install: gem install bundler
41
+ script: bundle exec rspec
42
+ notifications:
43
+ slack:
44
+ secure: I03SDv+IrKy3IkeGjjHUJ9VneFMiYpLzIgPOixGFO5zGVXgulwmun82KsrPSW5HkK1UEQCahfoXt1hNECPwxcdsY01q6LBYJQx1jD0q17bXHllN/q0C6lx3peRN0KqNAAOU3/mHZxLt3HFV+N3HsSnoxDMuSYJgKbjCL/QVG2L2UYT9vi+JRNM/thj8R6MWKWlOHemik40GbLr2anCOCqiALzxnJzh5nJyGj+9SGvjhHfQq/fAIrg1+Scu+UchG8d+1yS5JWsGTt1/JF08i+Ux4ceTQ7GNBNeA5cj/xuUzvRx6A85renxyTiZMKIL0+jeceUm8c+/46XFcq0F7/kJB36lwjFhX1JRphcu/VouRdEwW/BvH74wnyHtygqOpk0LH4shp7A1DIS1DlnBbeJxrR5hdMDnmV85kTU6w6H0BbncsYY/pH1fji1kgH6jCsdwqDlq4wLB8x8I+eZABBsfb/r55z/HnBmmxlvR7Rt3X5/yR3gJrsgRrDBBZ5LwYy1RSCDu76vMQqWGJKG03FdfqSNqmC5V4MC5Ez8yjGDW0Yle09mwvsL2c6fDXyDPCeB/gwNk+FvgLRXnv7C4BaBNEoG4JnCL/dwauoJNg0lB6uF34BEmYAhZIrdY1CI6BqPWnaVMJfcJSYekBVXDIELDnTFRWjaGU1y8dM6lNzDmYE=
@@ -0,0 +1,121 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ ## [0.12.0] - 2019-07-19
5
+ ### Added
6
+ - Support for **TOML** (`.toml`) format
7
+ - realized as a plugin (`Qonfig.plugin(:toml)`);
8
+ - provides `#save_to_toml`, `#load_from_toml`, `#expose_toml` methods and works in `#*_yaml`-like manner);
9
+ - Custom `bin/rspec` command:
10
+ - `bin/rspec -n` - run tests without plugin tests;
11
+ - `bin/rspec -w` - run all tests;
12
+ - Added more convinient aliases for `Qonfig::DataSet` instances:
13
+ - `#save_to_yaml` => `#dump_to_yaml`;
14
+ - `#save_to_json` => `#dump_to_json`;
15
+ - `#save_to_toml` => `#dump_to_toml`;
16
+ ### Changed
17
+ - Actualized development dependencies;
18
+
19
+ ## [0.11.0] - 2019-05-15
20
+ ### Added
21
+ - `#save_to_json` - save configurations to a json file (uses native `::JSON.generate` under the hood);
22
+ - `#save_to_yaml` - save configurations to a yaml file (uses native `::Psych.dump` under the hood);
23
+
24
+ ### Changed
25
+ - new `#to_h` signature: `#to_h(key_transformer:, value_transformer:)`
26
+ - `:key_transformer` - proc object used for key pre-processing (`-> (key) { key }` by default);
27
+ - `:value_transformer` - proc object used for value pre-processing (`-> (value) { value }` by default);
28
+
29
+ ## [0.10.0] - 2019-02-26
30
+ ### Added
31
+ - `#slice_value` - get a slice of config options as a hash set and fetch the required value using the given key set;
32
+
33
+ ## [0.9.0] - 2018-11-28
34
+ ### Added
35
+ - `#slice` - get a slice of config options as a hash set (works in a `#dig` manner);
36
+
37
+ ## [0.8.0] - 2018-11-21
38
+ ### Changed
39
+ - `expose_yaml`, `load_from_yaml`, `load_from_json` and `load_from_self` treats empty hash (`{}`)
40
+ as an option with empty hash value (previously treated as a nested setting without options);
41
+
42
+ ## [0.7.0] - 2018-10-20
43
+ ### Added
44
+ - `expose_yaml` - a command that provides an ability to define config settings
45
+ by loading them from a yaml file where the concrete settings depends on the chosen environment;
46
+
47
+ ## [0.6.0] - 2018-08-22
48
+ ### Added
49
+ - `#shared_config` - instance method that provides an access to the class level config
50
+ object from `Qonfig::Configurable` instances;
51
+
52
+ ## [0.5.0] - 2018-07-27
53
+ ### Added
54
+ - `load_from_json`- a command that provides an ability to define config settings
55
+ by loading them from a json file (in `load_from_yaml` manner);
56
+
57
+ ### Changed
58
+ - Support for Ruby 2.2 has ended;
59
+
60
+ ## [0.4.0] - 2018-06-24
61
+ ### Added
62
+ - Introduce Plugin Ecosystem (`Qonfig::Plugins`):
63
+ - load plugin: `Qonfig.plugin('plugin_name')` or `Qonfig.plugin(:plugin_name)`;
64
+ - get registered plugins: `Qonfig.plugins #=> array of strings`
65
+
66
+ ## [0.3.0] - 2018-06-13
67
+ ### Added
68
+ - Improved configuration process: `#configure` can take a hash as a configuration `[option key => option]`
69
+ map of values;
70
+
71
+ ### Changed
72
+ - `#clear!` causes `Qonfig::FrozenSettingsError` if config object is frozen;
73
+
74
+ ## [0.2.0] - 2018-06-07
75
+ ### Added
76
+ - Instant configuration via block `config = Config.new { |conf| <<your configuration code>> }`;
77
+ - `.load_from_env` command - an ability to define config settings by loading them from ENV variable;
78
+ - `.load_from_yaml` command - an ability to define config settings by loading them from a yaml file;
79
+ - `.load_from_self` command - an ability to load config definitions form the YAML
80
+ instructions written in the file where the config class is defined (`__END__` section);
81
+ - `#reload!` - an ability to reload config isntance after any config class changes and updates;
82
+ - `#clear!` - an ability to set all options to `nil`;
83
+ - `#dig` - an ability to fetch setting values in `Hash#dig` manner
84
+ (fails with `Qonfig::UnknownSettingError` when the required key does not exist);
85
+ - Settings as Predicates - an ability to check the boolean nature of the config setting by appending
86
+ the question mark symbol (`?`) at the end of setting name:
87
+ - `nil` and `false` setting values indicates `false`;
88
+ - other setting values indicates `true`;
89
+ - setting roots always returns `true`;
90
+ - examples:
91
+ - `config.settings.database.user # => nil`;
92
+ - `config.settings.database.user? # => false`;
93
+ - `config.settings.database.host # => 'google.com'`;
94
+ - `config.settings.database.host? # => true`;
95
+ - `config.settings.database? # => true (setting with nested option (setting root))`
96
+ - Support for ERB instructions in YAML;
97
+ - Support for `HashWithIndifferentAccess`-like behaviour;
98
+ - `Qonfig::Settings` instance method redefinition protection: the setting key can not
99
+ have a name that matches an any instance method name of `Qonfig::Settings`;
100
+ - Added `Qonfig::Configurable` mixin - configuration behaviour for any classes and modules
101
+ and their instances:
102
+ - all `Qonfig`-related features;
103
+ - different class-level and instance-level config objects;
104
+ - working class-level inheritance :);
105
+ - Full thread-safe implementation;
106
+
107
+ ### Changed
108
+ - Superclass of `Qonfig::FrozenSettingsError` (it was `Qonfig::Error` before):
109
+ - `ruby >= 2.5` - inherited from `::FrozenError`;
110
+ - `ruby < 2.5` - inherited from `::RuntimeError`;
111
+ - `.setting` will raise exceptions immediately:
112
+ - `.setting(key, ...) { ... }` - if setting key has incompatible type;
113
+ - `.compose(config_class)`- if composed config class is not a subtype of `Qonfig::DataSet`;
114
+
115
+ ### Fixed
116
+ - Recoursive hash representation with deep nested `Qonfig::Settings` values (infinite loop);
117
+ - Fixed re-assignment of the options with nested options (losing the nested options
118
+ due to the instance configuration). Now it causes `Qonfig::AmbigousSettingValueError`.
119
+
120
+ ## [0.1.0] - 2018-05-18
121
+ - Release :)
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
6
 
5
7
  # Specify your gem's dependencies in qonfig.gemspec
6
8
  gemspec
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Rustam Ibragimov
3
+ Copyright (c) 2018-2019 Rustam Ibragimov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,43 +1,1084 @@
1
- # Qonfig
1
+ # 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)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/qonfig`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Config. Defined as a class. Used as an instance. Support for inheritance and composition.
4
+ Lazy instantiation. Thread-safe. Command-style DSL. Extremely simple to define. Extremely simple to use. That's all.
6
5
 
7
6
  ## Installation
8
7
 
9
- Add this line to your application's Gemfile:
10
-
11
8
  ```ruby
12
9
  gem 'qonfig'
13
10
  ```
14
11
 
15
- And then execute:
12
+ ```shell
13
+ $ bundle install
14
+ # --- or ---
15
+ $ gem install 'qonfig'
16
+ ```
17
+
18
+ ```ruby
19
+ require 'qonfig'
20
+ ```
21
+
22
+ ## Usage
16
23
 
17
- $ bundle
24
+ - [Definition and Settings Access](#definition-and-access)
25
+ - [Configuration](#configuration)
26
+ - [Inheritance](#inheritance)
27
+ - [Composition](#composition)
28
+ - [Hash representation](#hash-representation)
29
+ - [Config reloading](#config-reloading) (reload config definitions and option values)
30
+ - [Clear options](#clear-options) (set to nil)
31
+ - [State freeze](#state-freeze)
32
+ - [Settings as Predicates](#settings-as-predicates)
33
+ - [Load from YAML file](#load-from-yaml-file)
34
+ - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs)
35
+ - [Load from JSON file](#load-from-json-file)
36
+ - [Load from ENV](#load-from-env)
37
+ - [Load from \_\_END\_\_](#load-from-__end__) (aka `load_from_self`)
38
+ - [Save to JSON file](#save-to-json-file) (`save_to_json`)
39
+ - [Save to YAML file](#save-to-yaml-file) (`save_to_yaml`)
40
+ - [Smart Mixin](#smart-mixin) (`Qonfig::Configurable`)
41
+ - [Plugins](#plugins)
42
+ - [toml](#plugin-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`)
43
+ ---
18
44
 
19
- Or install it yourself as:
45
+ ### Definition and Access
20
46
 
21
- $ gem install qonfig
47
+ ```ruby
48
+ # --- definition ---
49
+ class Config < Qonfig::DataSet
50
+ # nil by default
51
+ setting :project_id
22
52
 
23
- ## Usage
53
+ # nested setting
54
+ setting :vendor_api do
55
+ setting :host, 'app.service.com'
56
+ end
57
+
58
+ setting :enable_graphql, false
59
+
60
+ # nested setting reopening
61
+ setting :vendor_api do
62
+ setting :user, 'test_user'
63
+ end
64
+ end
65
+
66
+ config = Config.new # your configuration object instance
67
+
68
+ # --- setting access ---
69
+
70
+ # get option value via method
71
+ config.settings.project_id # => nil
72
+ config.settings.vendor_api.host # => 'app.service.com'
73
+ config.settings.vendor_api.user # => 'test_user'
74
+ config.settings.enable_graphql # => false
75
+
76
+ # get option value via index (with indifferent (string / symbol / mixed) access)
77
+ config.settings[:project_id] # => nil
78
+ config.settings[:vendor_api][:host] # => 'app.service.com'
79
+ config.settings[:vendor_api][:user] # => 'test_user'
80
+ config.settings[:enable_graphql] # => false
81
+
82
+ # get option value via index (with indifferent (string / symbol / mixed) access)
83
+ config.settings['project_id'] # => nil
84
+ config.settings['vendor_api']['host'] # => 'app.service.com'
85
+ config.settings['vendor_api']['user'] # => 'test_user'
86
+ config.settings['enable_graphql'] # => false
87
+
88
+ # get option value directly via index (with indifferent access)
89
+ config['project_id'] # => nil
90
+ config['enable_graphql'] # => false
91
+ config[:project_id] # => nil
92
+ config[:enable_graphql] # => false
93
+
94
+ # get option value in Hash#dig manner (and fail when the required key does not exist)
95
+ config.dig(:vendor_api, :host) # => 'app.service.com' # (key exists)
96
+ config.dig(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
97
+
98
+ # get a hash slice of setting options (and fail when the required key does not exist)
99
+ config.slice(:vendor_api) # => { 'vendor_api' => { 'host' => 'app_service', 'user' => 'test_user' } }
100
+ config.slice(:vendor_api, :user) # => { 'user' => 'test_user' }
101
+ config.slice(:project_api) # => Qonfig::UnknownSettingError # (key does not exist)
102
+ config.slice(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
103
+
104
+ # 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)
105
+ config.slice_value(:vendor_api) # => { 'host' => 'app_service', 'user' => 'test_user' }
106
+ config.slice_value(:vendor_api, :user) # => 'test_user'
107
+ config.slice_value(:project_api) # => Qonfig::UnknownSettingError # (key does not exist)
108
+ config.slice_value(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
109
+ ```
110
+
111
+ ---
112
+
113
+ ### Configuration
114
+
115
+ ```ruby
116
+ class Config < Qonfig::DataSet
117
+ setting :testing do
118
+ setting :engine, :rspec
119
+ setting :parallel, true
120
+ end
121
+
122
+ setting :geo_api do
123
+ setting :provider, :google_maps
124
+ end
125
+
126
+ setting :enable_middlewares, false
127
+ end
128
+
129
+ config = Config.new
130
+
131
+ # configure via proc
132
+ config.configure do |conf|
133
+ conf.enable_middlewares = true
134
+ conf.geo_api.provider = :yandex_maps
135
+ conf.testing.engine = :mini_test
136
+ end
137
+
138
+ # configure via settings object (by option name)
139
+ config.settings.enable_middlewares = false
140
+ config.settings.geo_api.provider = :apple_maps
141
+ config.settings.testing.engine = :ultra_test
142
+
143
+ # configure via settings object (by setting key)
144
+ config.settings[:enable_middlewares] = true
145
+ config.settings[:geo_api][:provider] = :rambler_maps
146
+ config.settings[:testing][:engine] = :mega_test
147
+
148
+ # instant configuration via proc
149
+ config = Config.new do |conf|
150
+ conf.enable_middlewares = false
151
+ conf.geo_api.provider = :amazon_maps
152
+ conf.testing.engine = :crypto_test
153
+ end
154
+
155
+ # using a hash
156
+ config = Config.new(
157
+ testing: { engine: :mini_test, parallel: false },
158
+ geo_api: { provider: :rambler_maps },
159
+ enable_middlewares: true
160
+ )
161
+ config.configure(enable_middlewares: false)
162
+
163
+ # using both hash and proc (proc has higher priority)
164
+ config = Config.new(enable_middlewares: true) do |conf|
165
+ conf.testing.parallel = true
166
+ end
167
+
168
+ config.configure(geo_api: { provider: nil }) do |conf|
169
+ conf.testing.engine = :rspec
170
+ end
171
+ ```
172
+
173
+ ---
174
+
175
+ ### Inheritance
176
+
177
+ ```ruby
178
+ class CommonConfig < Qonfig::DataSet
179
+ setting :uploader, :fog
180
+ end
181
+
182
+ class ProjectConfig < CommonConfig
183
+ setting :auth_provider, :github
184
+ end
185
+
186
+ project_config = ProjectConfig.new
187
+
188
+ # inherited setting
189
+ project_config.settings.uploader # => :fog
190
+
191
+ # own setting
192
+ project_config.settings.auth_provider # => :github
193
+ ```
194
+
195
+ ---
196
+
197
+ ### Composition
198
+
199
+ ```ruby
200
+ class SharedConfig < Qonfig::DataSet
201
+ setting :logger, Logger.new
202
+ end
203
+
204
+ class ServerConfig < Qonfig::DataSet
205
+ setting :port, 12345
206
+ setting :address, '0.0.0.0'
207
+ end
208
+
209
+ class DatabaseConfig < Qonfig::DataSet
210
+ setting :user, 'test'
211
+ setting :password, 'testpaswd'
212
+ end
213
+
214
+ class ProjectConfig < Qonfig::DataSet
215
+ compose SharedConfig
216
+
217
+ setting :server do
218
+ compose ServerConfig
219
+ end
220
+
221
+ setting :db do
222
+ compose DatabaseConfig
223
+ end
224
+ end
225
+
226
+ project_config = ProjectConfig.new
227
+
228
+ # fields from SharedConfig
229
+ project_config.settings.logger # => #<Logger:0x66f57048>
230
+
231
+ # fields from ServerConfig
232
+ project_config.settings.server.port # => 12345
233
+ project_config.settings.server.address # => '0.0.0.0'
234
+
235
+ # fields from DatabaseConfig
236
+ project_config.settings.db.user # => 'test'
237
+ project_config.settings.db.password # => 'testpaswd'
238
+ ```
239
+
240
+ ---
241
+
242
+ ### Hash representation
243
+
244
+ ```ruby
245
+ class Config < Qonfig::DataSet
246
+ setting :serializers do
247
+ setting :json do
248
+ setting :engine, :ok
249
+ end
250
+
251
+ setting :hash do
252
+ setting :engine, :native
253
+ end
254
+ end
255
+
256
+ setting :adapter do
257
+ setting :default, :memory_sync
258
+ end
259
+
260
+ setting :logger, Logger.new(STDOUT)
261
+ end
262
+
263
+ Config.new.to_h
264
+
265
+ {
266
+ "serializers": {
267
+ "json" => { "engine" => :ok },
268
+ "hash" => { "engine" => :native },
269
+ },
270
+ "adapter" => { "default" => :memory_sync },
271
+ "logger" => #<Logger:0x4b0d79fc>
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ### Config reloading
278
+
279
+ ```ruby
280
+ class Config < Qonfig::DataSet
281
+ setting :db do
282
+ setting :adapter, 'postgresql'
283
+ end
284
+
285
+ setting :logger, Logger.new(STDOUT)
286
+ end
287
+
288
+ config = Config.new
289
+
290
+ config.settings.db.adapter # => 'postgresql'
291
+ config.settings.logger # => #<Logger:0x00007ff9>
292
+
293
+ config.configure { |conf| conf.logger = nil } # redefine some settings (will be reloaded)
294
+
295
+ # re-define and append settings
296
+ class Config
297
+ setting :db do
298
+ setting :adapter, 'mongoid' # re-define defaults
299
+ end
300
+
301
+ setting :enable_api, false # append new setting
302
+ end
303
+
304
+ # reload settings
305
+ config.reload!
306
+
307
+ config.settings.db.adapter # => 'mongoid'
308
+ config.settings.logger # => #<Logger:0x00007ff9> (reloaded from defaults)
309
+ config.settings.enable_api # => false (new setting)
310
+
311
+ # reload with instant configuration
312
+ config.reload!(db: { adapter: 'oracle' }) do |conf|
313
+ conf.enable_api = true # changed instantly
314
+ end
315
+
316
+ config.settings.db.adapter # => 'oracle'
317
+ config.settings.logger = # => #<Logger:0x00007ff9>
318
+ config.settings.enable_api # => true # value from instant change
319
+ ```
320
+
321
+ ---
322
+
323
+ ### Clear options
324
+
325
+ ```ruby
326
+ class Config
327
+ setting :database do
328
+ setting :user
329
+ setting :password
330
+ end
331
+
332
+ setting :web_api do
333
+ setting :endpoint
334
+ end
335
+ end
336
+
337
+ config = Config.new do |conf|
338
+ conf.database.user = '0exp'
339
+ conf.database.password = 'test123'
340
+
341
+ conf.web_api.endpoint = '/api/'
342
+ end
343
+
344
+ config.settings.database.user # => '0exp'
345
+ config.settings.database.password # => 'test123'
346
+ config.settings.web_api.endpoint # => '/api'
347
+
348
+ # clear all options
349
+ config.clear!
350
+
351
+ config.settings.database.user # => nil
352
+ config.settings.database.password # => nil
353
+ config.settings.web_api.endpoint # => nil
354
+ ```
355
+
356
+ ---
357
+
358
+ ### State freeze
359
+
360
+ ```ruby
361
+ class Config < Qonfig::DataSet
362
+ setting :logger, Logger.new(STDOUT)
363
+ setting :worker, :sidekiq
364
+ setting :db do
365
+ setting :adapter, 'postgresql'
366
+ end
367
+ end
368
+
369
+ config = Config.new
370
+ config.freeze!
371
+
372
+ config.settings.logger = Logger.new(StringIO.new) # => Qonfig::FrozenSettingsError
373
+ config.settings.worker = :que # => Qonfig::FrozenSettingsError
374
+ config.settings.db.adapter = 'mongoid' # => Qonfig::FrozenSettingsError
375
+
376
+ config.reload! # => Qonfig::FrozenSettingsError
377
+ config.clear! # => Qonfig::FrozenSettingsError
378
+ ```
379
+
380
+ ---
381
+
382
+ ### Settings as Predicates
383
+
384
+ - predicate form: `?` at the end of setting name;
385
+ - `nil` and `false` setting values indicates `false`;
386
+ - other setting values indicates `true`;
387
+ - setting roots always returns `true`;
388
+
389
+ ```ruby
390
+ class Config < Qonfig::DataSet
391
+ setting :database do
392
+ setting :user
393
+ setting :host, 'google.com'
394
+
395
+ setting :engine do
396
+ setting :driver, 'postgres'
397
+ end
398
+ end
399
+ end
400
+
401
+ config = Config.new
402
+
403
+ # predicates
404
+ config.settings.database.user? # => false (nil => false)
405
+ config.settings.database.host? # => true ('google.com' => true)
406
+ config.settings.database.engine.driver? # => true ('postgres' => true)
407
+
408
+ # setting roots always returns true
409
+ config.settings.database? # => true
410
+ config.settings.database.engine? # => ture
411
+
412
+ config.configure do |conf|
413
+ conf.database.user = '0exp'
414
+ conf.database.host = false
415
+ conf.database.engine.driver = true
416
+ end
417
+
418
+ # predicates
419
+ config.settings.database.user? # => true ('0exp' => true)
420
+ config.settings.database.host? # => false (false => false)
421
+ config.settings.database.engine.driver? # => true (true => true)
422
+ ```
423
+
424
+ ---
425
+
426
+ ### Load from YAML file
427
+
428
+ - supports `ERB`;
429
+ - `:strict` mode (fail behaviour when the required yaml file doesnt exist):
430
+ - `true` (by default) - causes `Qonfig::FileNotFoundError`;
431
+ - `false` - do nothing, ignore current command;
432
+
433
+ ```yaml
434
+ # travis.yml
435
+
436
+ sudo: false
437
+ language: ruby
438
+ rvm:
439
+ - ruby-head
440
+ - jruby-head
441
+ ```
442
+
443
+ ```yaml
444
+ # project.yml
445
+
446
+ enable_api: false
447
+ Sidekiq/Scheduler:
448
+ enable: true
449
+ ```
450
+
451
+ ```yaml
452
+ # ruby_data.yml
453
+
454
+ version: <%= RUBY_VERSION %>
455
+ platform: <%= RUBY_PLATFORM %>
456
+ ```
457
+
458
+ ```ruby
459
+ class Config < Qonfig::DataSet
460
+ setting :ruby do
461
+ load_from_yaml 'ruby_data.yml'
462
+ end
463
+
464
+ setting :travis do
465
+ load_from_yaml 'travis.yml'
466
+ end
467
+
468
+ load_from_yaml 'project.yml'
469
+ end
470
+
471
+ config = Config.new
472
+
473
+ config.settings.travis.sudo # => false
474
+ config.settings.travis.language # => 'ruby'
475
+ config.settings.travis.rvm # => ['ruby-head', 'jruby-head']
476
+ config.settings.enable_api # => false
477
+ config.settings['Sidekiq/Scheduler']['enable'] #=> true
478
+ config.settings.ruby.version # => '2.5.1'
479
+ config.settings.ruby.platform # => 'x86_64-darwin17'
480
+ ```
481
+
482
+ ```ruby
483
+ # --- strict mode ---
484
+ class Config < Qonfig::DataSet
485
+ setting :nonexistent_yaml do
486
+ load_from_yaml 'nonexistent_yaml.yml', strict: true # true by default
487
+ end
488
+
489
+ setting :another_key
490
+ end
491
+
492
+ Config.new # => Qonfig::FileNotFoundError
493
+
494
+ # --- non-strict mode ---
495
+ class Config < Qonfig::DataSet
496
+ settings :nonexistent_yaml do
497
+ load_from_yaml 'nonexistent_yaml.yml', strict: false
498
+ end
499
+
500
+ setting :another_key
501
+ end
502
+
503
+ Config.new.to_h # => { "nonexistent_yaml" => {}, "another_key" => nil }
504
+ ```
505
+
506
+ ---
507
+
508
+ ### Expose YAML
509
+
510
+ - load configurations from YAML file in Rails-like manner (with environments);
511
+ - works in `load_from_yaml` manner;
512
+ - `via:` - how an environment will be determined:
513
+ - `:file_name`
514
+ - load configuration from YAML file that have an `:env` part in it's name;
515
+ - `:env_key`
516
+ - load configuration from YAML file;
517
+ - concrete configuration should be defined in the root key with `:env` name;
518
+ - `env:` - your environment name (must be a type of `String`, `Symbol` or `Numeric`);
519
+ - `strict:` - requires the existence of the file and/or key with the name of the used environment:
520
+ - `true`:
521
+ - file should exist;
522
+ - root key with `:env` name should exist (if `via: :env_key` is used);
523
+ - raises `Qonfig::ExposeError` if file does not contain the required env key (if `via: :env` key is used);
524
+ - raises `Qonfig::FileNotFoundError` if the required file does not exist;
525
+ - `false`:
526
+ - file is not required;
527
+ - root key with `:env` name is not required (if `via: :env_key` is used);
528
+
529
+ #### Environment is defined as a root key of YAML file
530
+
531
+ ```yaml
532
+ # config/project.yml
533
+
534
+ default: &default
535
+ enable_api_mode: true
536
+ google_key: 12345
537
+ window:
538
+ width: 100
539
+ height: 100
24
540
 
25
- TODO: Write usage instructions here
541
+ development:
542
+ <<: *default
26
543
 
27
- ## Development
544
+ test:
545
+ <<: *default
546
+ sidekiq_instrumentation: false
547
+
548
+ staging:
549
+ <<: *default
550
+ google_key: 777
551
+ enable_api_mode: false
552
+
553
+ production:
554
+ google_key: asd1-39sd-55aI-O92x
555
+ enable_api_mode: true
556
+ window:
557
+ width: 50
558
+ height: 150
559
+ ```
560
+
561
+ ```ruby
562
+ class Config < Qonfig::DataSet
563
+ expose_yaml 'config/project.yml', via: :env_key, env: :production # load from production env
564
+
565
+ # NOTE: in rails-like application you can use this:
566
+ expose_yaml 'config/project.yml', via: :env_key, env: Rails.env
567
+ end
568
+
569
+ config = Config.new
570
+
571
+ config.settings.enable_api_mode # => true (from :production subset of keys)
572
+ config.settings.google_key # => asd1-39sd-55aI-O92x (from :production subset of keys)
573
+ config.settings.window.width # => 50 (from :production subset of keys)
574
+ config.settings.window.height # => 150 (from :production subset of keys)
575
+ ```
576
+
577
+ #### Environment is defined as a part of YAML file name
578
+
579
+ ```yaml
580
+ # config/sidekiq.staging.yml
581
+
582
+ web:
583
+ username: staging_admin
584
+ password: staging_password
585
+ ```
586
+
587
+ ```yaml
588
+ # config/sidekiq.production.yml
589
+
590
+ web:
591
+ username: urj1o2
592
+ password: u192jd0ixz0
593
+ ```
594
+
595
+ ```ruby
596
+ class SidekiqConfig < Qonfig::DataSet
597
+ # NOTE: file name should be described WITHOUT environment part (in file name attribute)
598
+ expose_yaml 'config/sidekiq.yml', via: :file_name, env: :staging # load from staging env
599
+
600
+ # NOTE: in rails-like application you can use this:
601
+ expose_yaml 'config/sidekiq.yml', via: :file_name, env: Rails.env
602
+ end
603
+
604
+ config = SidekiqConfig.new
605
+
606
+ config.settings.web.username # => staging_admin (from sidekiq.staging.yml)
607
+ config.settings.web.password # => staging_password (from sidekiq.staging.yml)
608
+ ```
609
+
610
+ ---
611
+
612
+ ### Load from JSON file
613
+
614
+ - `:strict` mode (fail behaviour when the required yaml file doesnt exist):
615
+ - `true` (by default) - causes `Qonfig::FileNotFoundError`;
616
+ - `false` - do nothing, ignore current command;
617
+
618
+ ```json
619
+ // options.json
620
+
621
+ {
622
+ "user": "0exp",
623
+ "password": 12345,
624
+ "rubySettings": {
625
+ "allowedVersions": ["2.3", "2.4.2", "1.9.8"],
626
+ "gitLink": null,
627
+ "withAdditionals": false
628
+ }
629
+ }
630
+ ```
631
+
632
+ ```ruby
633
+ class Config < Qonfig::DataSet
634
+ load_from_json 'options.json'
635
+ end
636
+
637
+ config = Config.new
638
+
639
+ config.settings.user # => '0exp'
640
+ config.settings.password # => 12345
641
+ config.settings.rubySettings.allowedVersions # => ['2.3', '2.4.2', '1.9.8']
642
+ config.settings.rubySettings.gitLink # => nil
643
+ config.settings.rubySettings.withAdditionals # => false
644
+ ```
645
+
646
+ ```ruby
647
+ # --- strict mode ---
648
+ class Config < Qonfig::DataSet
649
+ setting :nonexistent_json do
650
+ load_from_json 'nonexistent_json.json', strict: true # true by default
651
+ end
652
+
653
+ setting :another_key
654
+ end
655
+
656
+ Config.new # => Qonfig::FileNotFoundError
657
+
658
+ # --- non-strict mode ---
659
+ class Config < Qonfig::DataSet
660
+ settings :nonexistent_json do
661
+ load_from_json 'nonexistent_json.json', strict: false
662
+ end
663
+
664
+ setting :another_key
665
+ end
666
+
667
+ Config.new.to_h # => { "nonexistent_json" => {}, "another_key" => nil }
668
+ ```
669
+
670
+ ---
671
+
672
+ ### Load from ENV
673
+
674
+ - `:convert_values` (`false` by default):
675
+ - `'t'`, `'T'`, `'true'`, `'TRUE'` - covnerts to `true`;
676
+ - `'f'`, `'F'`, `'false'`, `'FALSE'` - covnerts to `false`;
677
+ - `1`, `23` and etc - converts to `Integer`;
678
+ - `1.25`, `0.26` and etc - converts to `Float`;
679
+ - `1, 2, test`, `FALSE,Qonfig` (strings without quotes that contains at least one comma) -
680
+ converts to `Array` with recursively converted values;
681
+ - `'"please, test"'`, `"'test, please'"` (quoted strings) - converts to `String` without quotes;
682
+ - `:prefix` - load ENV variables which names starts with a prefix:
683
+ - `nil` (by default) - empty prefix;
684
+ - `Regexp` - names that match the regexp pattern;
685
+ - `String` - names which starts with a passed string;
686
+ - `:trim_prefix` (`false` by default);
687
+
688
+ ```ruby
689
+ # some env variables
690
+ ENV['QONFIG_BOOLEAN'] = 'true'
691
+ ENV['QONFIG_INTEGER'] = '0'
692
+ ENV['QONFIG_STRING'] = 'none'
693
+ ENV['QONFIG_ARRAY'] = '1, 2.5, t, f, TEST'
694
+ ENV['QONFIG_MESSAGE'] = '"Hello, Qonfig!"'
695
+ ENV['RUN_CI'] = '1'
696
+
697
+ class Config < Qonfig::DataSet
698
+ # nested
699
+ setting :qonfig do
700
+ load_from_env convert_values: true, prefix: 'QONFIG' # or /\Aqonfig.*\z/i
701
+ end
702
+
703
+ setting :trimmed do
704
+ load_from_env convert_values: true, prefix: 'QONFIG_', trim_prefix: true # trim prefix
705
+ end
706
+
707
+ # on the root
708
+ load_from_env
709
+ end
710
+
711
+ config = Config.new
712
+
713
+ # customized
714
+ config.settings['qonfig']['QONFIG_BOOLEAN'] # => true ('true' => true)
715
+ config.settings['qonfig']['QONFIG_INTEGER'] # => 0 ('0' => 0)
716
+ config.settings['qonfig']['QONFIG_STRING'] # => 'none'
717
+ config.settings['qonfig']['QONFIG_ARRAY'] # => [1, 2.5, true, false, 'TEST']
718
+ config.settings['qonfig']['QONFIG_MESSAGE'] # => 'Hello, Qonfig!'
719
+ config.settings['qonfig']['RUN_CI'] # => Qonfig::UnknownSettingError
720
+
721
+ # trimmed (and customized)
722
+ config.settings['trimmed']['BOOLEAN'] # => true ('true' => true)
723
+ config.settings['trimmed']['INTEGER'] # => 0 ('0' => 0)
724
+ config.settings['trimmed']['STRING'] # => 'none'
725
+ config.settings['trimmed']['ARRAY'] # => [1, 2.5, true, false, 'TEST']
726
+ config.settings['trimmed']['MESSAGE'] # => 'Hello, Qonfig!'
727
+ config.settings['trimmed']['RUN_CI'] # => Qonfig::UnknownSettingError
728
+
729
+ # default
730
+ config.settings['QONFIG_BOOLEAN'] # => 'true'
731
+ config.settings['QONFIG_INTEGER'] # => '0'
732
+ config.settings['QONFIG_STRING'] # => 'none'
733
+ config.settings['QONFIG_ARRAY'] # => '1, 2.5, t, f, TEST'
734
+ config.settings['QONFIG_MESSAGE'] # => '"Hello, Qonfig!"'
735
+ config.settings['RUN_CI'] # => '1'
736
+ ```
737
+
738
+ ---
739
+
740
+ ### Load from \_\_END\_\_
741
+
742
+ - aka `load_from_self`
743
+
744
+ ```ruby
745
+ class Config < Qonfig::DataSet
746
+ load_from_self # on the root
747
+
748
+ setting :nested do
749
+ load_from_self # nested
750
+ end
751
+ end
752
+
753
+ config = Config.new
754
+
755
+ # on the root
756
+ config.settings.ruby_version # => '2.5.1'
757
+ config.settings.secret_key # => 'top-mega-secret'
758
+ config.settings.api_host # => 'super.puper-google.com'
759
+ config.settings.connection_timeout.seconds # => 10
760
+ config.settings.connection_timeout.enabled # => false
761
+
762
+ # nested
763
+ config.settings.nested.ruby_version # => '2.5.1'
764
+ config.settings.nested.secret_key # => 'top-mega-secret'
765
+ config.settings.nested.api_host # => 'super.puper-google.com'
766
+ config.settings.nested.connection_timeout.seconds # => 10
767
+ config.settings.nested.connection_timeout.enabled # => false
768
+
769
+ __END__
770
+
771
+ ruby_version: <%= RUBY_VERSION %>
772
+ secret_key: top-mega-secret
773
+ api_host: super.puper-google.com
774
+ connection_timeout:
775
+ seconds: 10
776
+ enabled: false
777
+ ```
778
+
779
+ ---
780
+
781
+ ### Save to JSON file
782
+
783
+ - `#save_to_json` - represents config object as a json structure and saves it to a file:
784
+ - uses native `::JSON.generate` under the hood;
785
+ - writes new file (or rewrites existing file);
786
+ - attributes:
787
+ - `:path` - (required) - file path;
788
+ - `:options` - (optional) - native `::JSON.generate` options (from stdlib):
789
+ - `:indent` - `" "` by default;
790
+ - `:space` - `" "` by default/
791
+ - `:object_nl` - `"\n"` by default;
792
+ - `&value_preprocessor` - (optional) - value pre-processor;
793
+
794
+ #### Without value preprocessing (standard usage)
795
+
796
+ ```ruby
797
+ class AppConfig < Qonfig::DataSet
798
+ setting :server do
799
+ setting :address, 'localhost'
800
+ setting :port, 12_345
801
+ end
802
+
803
+ setting :enabled, true
804
+ end
805
+
806
+ config = AppConfig.new
807
+
808
+ # NOTE: save to json file
809
+ config.save_to_json(path: 'config.json')
810
+ ```
811
+
812
+ ```json
813
+ {
814
+ "sentry": {
815
+ "address": "localhost",
816
+ "port": 12345
817
+ },
818
+ "enabled": true
819
+ }
820
+ ```
821
+
822
+ #### With value preprocessing and custom options
823
+
824
+ ```ruby
825
+ class AppConfig < Qonfig::DataSet
826
+ setting :server do
827
+ setting :address, 'localhost'
828
+ setting :port, 12_345
829
+ end
830
+
831
+ setting :enabled, true
832
+ setting :dynamic, -> { 1 + 2 }
833
+ end
834
+
835
+ config = AppConfig.new
836
+
837
+ # NOTE: save to json file with custom options (no spaces / no new line / no indent; call procs)
838
+ config.save_to_json(path: 'config.json', options: { indent: '', space: '', object_nl: '' }) do |value|
839
+ value.is_a?(Proc) ? value.call : value
840
+ end
841
+ ```
842
+
843
+ ```json
844
+ // no spaces / no new line / no indent / calculated "dynamic" setting key
845
+ {"sentry":{"address":"localhost","port":12345},"enabled":true,"dynamic":3}
846
+ ```
847
+
848
+ ---
849
+
850
+ ### Save to YAML file
851
+
852
+ - `#save_to_yaml` - represents config object as a yaml structure and saves it to a file:
853
+ - uses native `::Psych.dump` under the hood;
854
+ - writes new file (or rewrites existing file);
855
+ - attributes:
856
+ - `:path` - (required) - file path;
857
+ - `:options` - (optional) - native `::Psych.dump` options (from stdlib):
858
+ - `:indentation` - `2` by default;
859
+ - `:line_width` - `-1` by default;
860
+ - `:canonical` - `false` by default;
861
+ - `:header` - `false` by default;
862
+ - `:symbolize_keys` - (non-native option) - `false` by default;
863
+ - `&value_preprocessor` - (optional) - value pre-processor;
864
+
865
+ #### Without value preprocessing (standard usage)
866
+
867
+ ```ruby
868
+ class AppConfig < Qonfig::DataSet
869
+ setting :server do
870
+ setting :address, 'localhost'
871
+ setting :port, 12_345
872
+ end
873
+
874
+ setting :enabled, true
875
+ end
876
+
877
+ config = AppConfig.new
878
+
879
+ # NOTE: save to yaml file
880
+ config.save_to_yaml(path: 'config.yml')
881
+ ```
882
+
883
+ ```yaml
884
+ ---
885
+ server:
886
+ address: localhost
887
+ port: 12345
888
+ enabled: true
889
+ ```
890
+
891
+ #### With value preprocessing and custom options
892
+
893
+ ```ruby
894
+ class AppConfig < Qonfig::DataSet
895
+ setting :server do
896
+ setting :address, 'localhost'
897
+ setting :port, 12_345
898
+ end
899
+
900
+ setting :enabled, true
901
+ setting :dynamic, -> { 5 + 5 }
902
+ end
903
+
904
+ config = AppConfig.new
905
+
906
+ # NOTE: save to yaml file with custom options (add yaml version header; call procs)
907
+ config.save_to_yaml(path: 'config.yml', options: { header: true }) do |value|
908
+ value.is_a?(Proc) ? value.call : value
909
+ end
910
+ ```
911
+
912
+ ```yaml
913
+ # yaml version header / calculated "dynamic" setting key
914
+ %YAML 1.1
915
+ ---
916
+ server:
917
+ address: localhost
918
+ port: 12345
919
+ enabled: true
920
+ dynamic: 10
921
+ ```
922
+
923
+ ---
924
+
925
+ ### Smart Mixin
926
+
927
+ - class-level:
928
+ - `.configuration` - settings definitions;
929
+ - `.configure` - configuration;
930
+ - `.config` - config object;
931
+ - settings definitions are inheritable;
932
+ - instance-level:
933
+ - `#configure` - configuration;
934
+ - `#config` - config object;
935
+ - `#shared_config` - class-level config object;
936
+
937
+ ```ruby
938
+ # --- usage ---
939
+
940
+ class Application
941
+ # make configurable
942
+ include Qonfig::Configurable
943
+
944
+ configuration do
945
+ setting :user
946
+ setting :password
947
+ end
948
+ end
949
+
950
+ app = Application.new
951
+
952
+ # class-level config
953
+ Application.config.settings.user # => nil
954
+ Application.config.settings.password # => nil
955
+
956
+ # instance-level config
957
+ app.config.settings.user # => nil
958
+ app.config.settings.password # => nil
959
+
960
+ # access to the class level config from an instance
961
+ app.shared_config.settings.user # => nil
962
+ app.shared_config.settings.password # => nil
963
+
964
+ # class-level configuration
965
+ Application.configure do |conf|
966
+ conf.user = '0exp'
967
+ conf.password = 'test123'
968
+ end
969
+
970
+ # instance-level configuration
971
+ app.configure do |conf|
972
+ conf.user = 'admin'
973
+ conf.password = '123test'
974
+ end
975
+
976
+ # class has own config object
977
+ Application.config.settings.user # => '0exp'
978
+ Application.config.settings.password # => 'test123'
979
+
980
+ # instance has own config object
981
+ app.config.settings.user # => 'admin'
982
+ app.config.settings.password # => '123test'
983
+
984
+ # access to the class level config from an instance
985
+ app.shared_config.settings.user # => '0exp'
986
+ app.shared_config.settings.password # => 'test123'
987
+
988
+ # and etc... (all Qonfig-related features)
989
+ ```
990
+
991
+ ```ruby
992
+ # --- inheritance ---
993
+
994
+ class BasicApplication
995
+ # make configurable
996
+ include Qonfig::Configurable
997
+
998
+ configuration do
999
+ setting :user
1000
+ setting :pswd
1001
+ end
1002
+
1003
+ configure do |conf|
1004
+ conf.user = 'admin'
1005
+ conf.pswd = 'admin'
1006
+ end
1007
+ end
1008
+
1009
+ class GeneralApplication < BasicApplication
1010
+ # extend inherited definitions
1011
+ configuration do
1012
+ setting :db do
1013
+ setting :adapter
1014
+ end
1015
+ end
1016
+
1017
+ configure do |conf|
1018
+ conf.user = '0exp' # .user inherited from BasicApplication
1019
+ conf.pswd = '123test' # .pswd inherited from BasicApplication
1020
+ conf.db.adapter = 'pg'
1021
+ end
1022
+ end
1023
+
1024
+ BasicApplication.config.to_h
1025
+ { 'user' => 'admin', 'pswd' => 'admin' }
1026
+
1027
+ GeneralApplication.config.to_h
1028
+ { 'user' => '0exp', 'pswd' => '123test', 'db' => { 'adapter' => 'pg' } }
1029
+
1030
+ # and etc... (all Qonfig-related features)
1031
+ ```
1032
+
1033
+ ---
1034
+
1035
+ ### Plugins
1036
+
1037
+ ```ruby
1038
+ # --- show names of registered plugins ---
1039
+ Qonfig.plugins # => array of strings
1040
+
1041
+ # --- load specific plugin ---
1042
+ Qonfig.plugin(:plugin_name) # or Qonfig.plugin('plugin_name')
1043
+ ```
1044
+
1045
+ ---
1046
+
1047
+ ### Plugins: toml
1048
+
1049
+ - adds support for `toml` format ([specification](https://github.com/toml-lang/toml));
1050
+ - depends on `toml-rb` gem;
1051
+ - provides `load_from_toml` (works in `load_from_yaml` manner [doc](#load-from-yaml-file));
1052
+ - provides `save_to_toml` (works in `save_to_yaml` manner [doc](#save-to-yaml-file)) (`toml-rb` has no native options);
1053
+ - provides `expose_toml` (works in `expose_yaml` manner [doc](#expose-yaml));
1054
+
1055
+ ```ruby
1056
+ require 'toml-rb'
1057
+ Qonfig.plugin(:toml)
1058
+ # and use :)
1059
+ ```
1060
+ ---
28
1061
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
1062
+ ## Roadmap
30
1063
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
1064
+ - support for TOML format;
1065
+ - explicit "settings" object;
1066
+ - validation layer;
1067
+ - distributed configuration server;
1068
+ - support for Rails-like secrets;
32
1069
 
33
1070
  ## Contributing
34
1071
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/qonfig. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
1072
+ - Fork it ( https://github.com/0exp/qonfig/fork )
1073
+ - Create your feature branch (`git checkout -b feature/my-new-feature`)
1074
+ - Commit your changes (`git commit -am 'Add some feature'`)
1075
+ - Push to the branch (`git push origin feature/my-new-feature`)
1076
+ - Create new Pull Request
36
1077
 
37
1078
  ## License
38
1079
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1080
+ Released under MIT License.
40
1081
 
41
- ## Code of Conduct
1082
+ ## Authors
42
1083
 
43
- Everyone interacting in the Qonfig project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/qonfig/blob/master/CODE_OF_CONDUCT.md).
1084
+ [Rustam Ibragimov](https://github.com/0exp)