@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7f383ceef309126a60c17c1383cdfd3de9445aa5978ccb80374226dfd17e83c
|
4
|
+
data.tar.gz: 6f2f868550a9822b4feb8daff758a2a91d486903f9e57726c1045725fc658e55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf030ed32f26241ebe6a8717e02e3b705d5f31e666c1096f19776c14e2db92f251e08ee12e668831a69bea51ce2fb40288791dbc4d8dfd19bc9fdd10c0505db8
|
7
|
+
data.tar.gz: 67e3f1712357a8de30e651271b8ca2dd2590af18cedd42ee57d48b86a755bfe15a0ea7ae59fea448212ae5fa798b19a12364acaa2fde588fc5778c33ef4d25c1
|
@@ -1,6 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## [0.13.0] - 2019-08-13
|
5
|
+
### Added
|
6
|
+
- Iteration over setting keys (`#each_setting { |key, value| }`, `#deep_each_setting { |key, value| }`);
|
7
|
+
- Brand new `Validation API`;
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- Actualized development dependencies;
|
11
|
+
|
4
12
|
## [0.12.0] - 2019-07-19
|
5
13
|
### Added
|
6
14
|
- Support for **TOML** (`.toml`) format
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# Qonfig · [](https://badge.fury.io/rb/qonfig) [](https://travis-ci.org/0exp/qonfig) [](https://coveralls.io/github/0exp/qonfig?branch=master)
|
2
2
|
|
3
3
|
Config. Defined as a class. Used as an instance. Support for inheritance and composition.
|
4
|
-
Lazy instantiation. Thread-safe. Command-style DSL.
|
4
|
+
Lazy instantiation. Thread-safe. Command-style DSL. Validation layer. Support for **YAML**, **TOML**, **JSON**, **\_\_END\_\_**, **ENV**.
|
5
|
+
Extremely simple to define. Extremely simple to use. That's all? **NOT** :)
|
5
6
|
|
6
7
|
## Installation
|
7
8
|
|
@@ -21,27 +22,38 @@ require 'qonfig'
|
|
21
22
|
|
22
23
|
## Usage
|
23
24
|
|
24
|
-
- [Definition
|
25
|
-
- [
|
26
|
-
- [
|
27
|
-
- [
|
28
|
-
- [
|
29
|
-
- [
|
30
|
-
- [
|
31
|
-
- [
|
32
|
-
- [
|
33
|
-
- [
|
34
|
-
- [
|
35
|
-
- [
|
36
|
-
- [
|
37
|
-
- [
|
38
|
-
- [
|
39
|
-
- [
|
40
|
-
- [
|
25
|
+
- [Definition](#definition)
|
26
|
+
- [Definition and Settings Access](#definition-and-access)
|
27
|
+
- [Configuration](#configuration)
|
28
|
+
- [Inheritance](#inheritance)
|
29
|
+
- [Composition](#composition)
|
30
|
+
- [Hash representation](#hash-representation)
|
31
|
+
- [Smart Mixin](#smart-mixin) (`Qonfig::Configurable`)
|
32
|
+
- [Interaction](#interaction)
|
33
|
+
- [Iteration over setting keys](#iteration-over-setting-keys) (`#each_setting`, `#deep_each_setting`)
|
34
|
+
- [Config reloading](#config-reloading) (reload config definitions and option values)
|
35
|
+
- [Clear options](#clear-options) (set to nil)
|
36
|
+
- [State freeze](#state-freeze)
|
37
|
+
- [Settings as Predicates](#settings-as-predicates)
|
38
|
+
- [Validation](#validation)
|
39
|
+
- [Key search pattern](#key-search-pattern)
|
40
|
+
- [Proc-based validation](#proc-based-validation)
|
41
|
+
- [Method-based validation](#method-based-validation)
|
42
|
+
- [Predefined validations](#predefined-validations)
|
43
|
+
- [Work with files](#work-with-files)
|
44
|
+
- [Load from YAML file](#load-from-yaml-file)
|
45
|
+
- [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs)
|
46
|
+
- [Load from JSON file](#load-from-json-file)
|
47
|
+
- [Load from ENV](#load-from-env)
|
48
|
+
- [Load from \_\_END\_\_](#load-from-__end__) (aka `load_from_self`)
|
49
|
+
- [Save to JSON file](#save-to-json-file) (`save_to_json`)
|
50
|
+
- [Save to YAML file](#save-to-yaml-file) (`save_to_yaml`)
|
41
51
|
- [Plugins](#plugins)
|
42
|
-
- [toml](#
|
52
|
+
- [toml](#plugins-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`)
|
43
53
|
---
|
44
54
|
|
55
|
+
## Definition
|
56
|
+
|
45
57
|
### Definition and Access
|
46
58
|
|
47
59
|
```ruby
|
@@ -274,6 +286,164 @@ Config.new.to_h
|
|
274
286
|
|
275
287
|
---
|
276
288
|
|
289
|
+
### Smart Mixin
|
290
|
+
|
291
|
+
- class-level:
|
292
|
+
- `.configuration` - settings definitions;
|
293
|
+
- `.configure` - configuration;
|
294
|
+
- `.config` - config object;
|
295
|
+
- settings definitions are inheritable;
|
296
|
+
- instance-level:
|
297
|
+
- `#configure` - configuration;
|
298
|
+
- `#config` - config object;
|
299
|
+
- `#shared_config` - class-level config object;
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
# --- usage ---
|
303
|
+
|
304
|
+
class Application
|
305
|
+
# make configurable
|
306
|
+
include Qonfig::Configurable
|
307
|
+
|
308
|
+
configuration do
|
309
|
+
setting :user
|
310
|
+
setting :password
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
app = Application.new
|
315
|
+
|
316
|
+
# class-level config
|
317
|
+
Application.config.settings.user # => nil
|
318
|
+
Application.config.settings.password # => nil
|
319
|
+
|
320
|
+
# instance-level config
|
321
|
+
app.config.settings.user # => nil
|
322
|
+
app.config.settings.password # => nil
|
323
|
+
|
324
|
+
# access to the class level config from an instance
|
325
|
+
app.shared_config.settings.user # => nil
|
326
|
+
app.shared_config.settings.password # => nil
|
327
|
+
|
328
|
+
# class-level configuration
|
329
|
+
Application.configure do |conf|
|
330
|
+
conf.user = '0exp'
|
331
|
+
conf.password = 'test123'
|
332
|
+
end
|
333
|
+
|
334
|
+
# instance-level configuration
|
335
|
+
app.configure do |conf|
|
336
|
+
conf.user = 'admin'
|
337
|
+
conf.password = '123test'
|
338
|
+
end
|
339
|
+
|
340
|
+
# class has own config object
|
341
|
+
Application.config.settings.user # => '0exp'
|
342
|
+
Application.config.settings.password # => 'test123'
|
343
|
+
|
344
|
+
# instance has own config object
|
345
|
+
app.config.settings.user # => 'admin'
|
346
|
+
app.config.settings.password # => '123test'
|
347
|
+
|
348
|
+
# access to the class level config from an instance
|
349
|
+
app.shared_config.settings.user # => '0exp'
|
350
|
+
app.shared_config.settings.password # => 'test123'
|
351
|
+
|
352
|
+
# and etc... (all Qonfig-related features)
|
353
|
+
```
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
# --- inheritance ---
|
357
|
+
|
358
|
+
class BasicApplication
|
359
|
+
# make configurable
|
360
|
+
include Qonfig::Configurable
|
361
|
+
|
362
|
+
configuration do
|
363
|
+
setting :user
|
364
|
+
setting :pswd
|
365
|
+
end
|
366
|
+
|
367
|
+
configure do |conf|
|
368
|
+
conf.user = 'admin'
|
369
|
+
conf.pswd = 'admin'
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
class GeneralApplication < BasicApplication
|
374
|
+
# extend inherited definitions
|
375
|
+
configuration do
|
376
|
+
setting :db do
|
377
|
+
setting :adapter
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
configure do |conf|
|
382
|
+
conf.user = '0exp' # .user inherited from BasicApplication
|
383
|
+
conf.pswd = '123test' # .pswd inherited from BasicApplication
|
384
|
+
conf.db.adapter = 'pg'
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
BasicApplication.config.to_h
|
389
|
+
{ 'user' => 'admin', 'pswd' => 'admin' }
|
390
|
+
|
391
|
+
GeneralApplication.config.to_h
|
392
|
+
{ 'user' => '0exp', 'pswd' => '123test', 'db' => { 'adapter' => 'pg' } }
|
393
|
+
|
394
|
+
# and etc... (all Qonfig-related features)
|
395
|
+
```
|
396
|
+
|
397
|
+
---
|
398
|
+
|
399
|
+
|
400
|
+
## Interaction
|
401
|
+
|
402
|
+
---
|
403
|
+
|
404
|
+
### Iteration over setting keys
|
405
|
+
|
406
|
+
- `#each_setting { |key, value| }`
|
407
|
+
- iterates over the root setting keys;
|
408
|
+
- `#deep_each_setting { |key, value| }`
|
409
|
+
- iterates over all setting keys (deep inside);
|
410
|
+
- key object is represented as a string of `.`-joined keys;
|
411
|
+
|
412
|
+
```ruby
|
413
|
+
class Config < Qonfig::DataSet
|
414
|
+
setting :db do
|
415
|
+
setting :creds do
|
416
|
+
setting :user, 'D@iVeR'
|
417
|
+
setting :password, 'test123',
|
418
|
+
setting :data, test: false
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
setting :telegraf_url, 'udp://localhost:8094'
|
423
|
+
setting :telegraf_prefix, 'test'
|
424
|
+
end
|
425
|
+
|
426
|
+
config = Config.new
|
427
|
+
|
428
|
+
# 1. #each_setting
|
429
|
+
config.each_setting { |key, value| { key => value } }
|
430
|
+
# result of each step:
|
431
|
+
{ 'db' => <Qonfig::Settings:0x00007ff8> }
|
432
|
+
{ 'telegraf_url' => 'udp://localhost:8094' }
|
433
|
+
{ 'telegraf_prefix' => 'test' }
|
434
|
+
|
435
|
+
# 2. #deep_each_setting
|
436
|
+
config.deep_each_setting { |key, value| { key => value } }
|
437
|
+
# result of each step:
|
438
|
+
{ 'db.creds.user' => 'D@iveR' }
|
439
|
+
{ 'db.creds.password' => 'test123' }
|
440
|
+
{ 'db.creds.data' => { test: false } }
|
441
|
+
{ 'telegraf_url' => 'udp://localhost:8094' }
|
442
|
+
{ 'telegraf_prefix' => 'test' }
|
443
|
+
```
|
444
|
+
|
445
|
+
---
|
446
|
+
|
277
447
|
### Config reloading
|
278
448
|
|
279
449
|
```ruby
|
@@ -423,6 +593,264 @@ config.settings.database.engine.driver? # => true (true => true)
|
|
423
593
|
|
424
594
|
---
|
425
595
|
|
596
|
+
## Validation
|
597
|
+
|
598
|
+
Qonfig provides a lightweight DSL for defining validations and works in all cases when setting values are initialized or mutated.
|
599
|
+
Settings are validated as keys (matched with a [specific string pattern](#key-search-patern)).
|
600
|
+
You can validate both a set of keys and each key separately.
|
601
|
+
If you want to check the config object completely you can define a custom validation.
|
602
|
+
|
603
|
+
**Features**:
|
604
|
+
|
605
|
+
- is invoked on any mutation of any setting key
|
606
|
+
- during dataset instantiation;
|
607
|
+
- when assigning new values;
|
608
|
+
- when calling `#reload!`;
|
609
|
+
- when calling `#clear!`;
|
610
|
+
|
611
|
+
- provides special [key search pattern](#key-search-pattern) for matching setting key names;
|
612
|
+
- uses the [key search pattern](#key-search-pattern) for definging what the setting key should be validated;
|
613
|
+
- you can define your own custom validation logic and validate dataset instance completely;
|
614
|
+
- validation logic should return **truthy** or **falsy** value;
|
615
|
+
|
616
|
+
- supprots two validation techniques (**proc-based** and **dataset-method-based**)
|
617
|
+
- **proc-based** (`setting validation`)
|
618
|
+
```ruby
|
619
|
+
validate 'db.user' do |value|
|
620
|
+
value.is_a?(String)
|
621
|
+
end
|
622
|
+
```
|
623
|
+
- **proc-based** (`dataset validation`)
|
624
|
+
```ruby
|
625
|
+
validate do
|
626
|
+
settings.user == User[1]
|
627
|
+
end
|
628
|
+
```
|
629
|
+
- **dataset-method-based** (`setting validation`)
|
630
|
+
```ruby
|
631
|
+
validate 'db.user', by: :check_user
|
632
|
+
|
633
|
+
def check_user(value)
|
634
|
+
value.is_a?(String)
|
635
|
+
end
|
636
|
+
```
|
637
|
+
- **dataset-method-based** (`dataset validation`)
|
638
|
+
```ruby
|
639
|
+
validate by: :check_config
|
640
|
+
|
641
|
+
def check_config
|
642
|
+
settings.user == User[1]
|
643
|
+
end
|
644
|
+
```
|
645
|
+
|
646
|
+
- provides a set of standard validations:
|
647
|
+
- `integer`
|
648
|
+
- `float`
|
649
|
+
- `numeric`
|
650
|
+
- `big_decimal`
|
651
|
+
- `boolean`
|
652
|
+
- `string`
|
653
|
+
- `symbol`
|
654
|
+
- `text` (string or symbol)
|
655
|
+
- `array`
|
656
|
+
- `hash`
|
657
|
+
- `proc`
|
658
|
+
- `class`
|
659
|
+
- `module`
|
660
|
+
- `not_nil`
|
661
|
+
|
662
|
+
---
|
663
|
+
|
664
|
+
### Key search pattern
|
665
|
+
|
666
|
+
**Key search pattern** works according to the following rules:
|
667
|
+
|
668
|
+
- works in `RabbitMQ`-like key pattern ruleses;
|
669
|
+
- has a string format;
|
670
|
+
- nested configs are defined by a set of keys separated by `.`-symbol;
|
671
|
+
- if the setting key name at the current nesting level does not matter - use `*`;
|
672
|
+
- if both the setting key name and nesting level does not matter - use `#`
|
673
|
+
- examples:
|
674
|
+
- `db.settings.user` - matches to `db.settings.user` setting;
|
675
|
+
- `db.settings.*` - matches to all setting keys inside `db.settings` group of settings;
|
676
|
+
- `db.*.user` - matches to all `user` setting keys at the first level of `db` group of settings;
|
677
|
+
- `#.user` - matches to all `user` setting keys;
|
678
|
+
- `service.#.password` - matches to all `password` setting keys at all levels of `service` group of settings;
|
679
|
+
- `#` - matches to ALL setting keys;
|
680
|
+
- `*` - matches to all setting keys at the root level;
|
681
|
+
- and etc;
|
682
|
+
|
683
|
+
---
|
684
|
+
|
685
|
+
### Proc-based validation
|
686
|
+
|
687
|
+
- your proc should return truthy value or falsy value;
|
688
|
+
- how to validate setting keys:
|
689
|
+
- define proc with attribute: `validate 'your.setting.path' do |value|; end`
|
690
|
+
- proc will receive setting value;
|
691
|
+
- how to validate dataset instance:
|
692
|
+
- define proc without setting key pattern: `validate do; end`
|
693
|
+
|
694
|
+
```ruby
|
695
|
+
class Config < Qonfig::DataSet
|
696
|
+
setting :db do
|
697
|
+
setting :user, 'D@iVeR'
|
698
|
+
setting :password, 'test123'
|
699
|
+
end
|
700
|
+
|
701
|
+
setting :service do
|
702
|
+
setting :address, 'google.ru'
|
703
|
+
setting :protocol, 'https'
|
704
|
+
|
705
|
+
setting :creds do
|
706
|
+
seting :admin, 'D@iVeR'
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
setting :enabled, false
|
711
|
+
|
712
|
+
# validates:
|
713
|
+
# - db.password
|
714
|
+
validate 'db.password' do |value|
|
715
|
+
value.is_a?(String)
|
716
|
+
end
|
717
|
+
|
718
|
+
# validates:
|
719
|
+
# - service.address
|
720
|
+
# - service.protocol
|
721
|
+
# - service.creds.user
|
722
|
+
validate 'service.#' do |value|
|
723
|
+
value.is_a?(String)
|
724
|
+
end
|
725
|
+
|
726
|
+
# validates:
|
727
|
+
# - dataset instance
|
728
|
+
validate do # NOTE: no setting key pattern
|
729
|
+
settings.enabled == false
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
config = Config.new
|
734
|
+
config.settings.db.password = 123 # => Qonfig::ValidationError (should be a string)
|
735
|
+
config.settings.service.address = 123 # => Qonfig::ValidationError (should be a string)
|
736
|
+
config.settings.service.protocol = :http # => Qonfig::ValidationError (should be a string)
|
737
|
+
config.settings.service.creds.admin = :billikota # => Qonfig::ValidationError (should be a string)
|
738
|
+
config.settings.enabled = true # => Qonfig::ValidationError (isnt `true`)
|
739
|
+
```
|
740
|
+
|
741
|
+
---
|
742
|
+
|
743
|
+
### Method-based validation
|
744
|
+
|
745
|
+
- method should return truthy value or falsy value;
|
746
|
+
- how to validate setting keys:
|
747
|
+
- define validation: `validate 'db.*.user', by: :your_custom_method`;
|
748
|
+
- define your method with attribute: `def your_custom_method(setting_value); end`
|
749
|
+
- how to validate config instance
|
750
|
+
- define validation: `validate by: :your_custom_method`
|
751
|
+
- define your method without attributes: `def your_custom_method; end`
|
752
|
+
|
753
|
+
```ruby
|
754
|
+
class Config < Qonfig::DataSet
|
755
|
+
setting :services do
|
756
|
+
setting :counts do
|
757
|
+
setting :google, 2
|
758
|
+
setting :rambler, 3
|
759
|
+
end
|
760
|
+
|
761
|
+
setting :minimals do
|
762
|
+
setting :google, 1
|
763
|
+
setting :rambler, 0
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
setting :enabled, true
|
768
|
+
|
769
|
+
# validates:
|
770
|
+
# - services.counts.google
|
771
|
+
# - services.counts.rambler
|
772
|
+
# - services.minimals.google
|
773
|
+
# - services.minimals.rambler
|
774
|
+
validate 'services.#', by: :check_presence
|
775
|
+
|
776
|
+
# validates:
|
777
|
+
# - dataset instance
|
778
|
+
validate by: :check_state # NOTE: no setting key pattern
|
779
|
+
|
780
|
+
def check_presence(value)
|
781
|
+
value.is_a?(Numeric) && value > 0
|
782
|
+
end
|
783
|
+
|
784
|
+
def check_state
|
785
|
+
settings.enabled.is_a?(TrueClass) || settings.enabled.is_a?(FalseClass)
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
789
|
+
config = Config.new
|
790
|
+
|
791
|
+
config.settings.counts.google = 0 # => Qonfig::ValidationError (< 0)
|
792
|
+
config.settings.counts.rambler = nil # => Qonfig::ValidationError (should be a numeric)
|
793
|
+
config.settings.minimals.google = -1 # => Qonfig::ValidationError (< 0)
|
794
|
+
config.settings.minimals.rambler = 'no' # => Qonfig::ValidationError (should be a numeric)
|
795
|
+
config.settings.enabled = nil # => Qonfig::ValidationError (should be a boolean)
|
796
|
+
```
|
797
|
+
|
798
|
+
---
|
799
|
+
|
800
|
+
### Predefined validations
|
801
|
+
|
802
|
+
- DSL: `validate 'key.pattern', :predefned_validator`
|
803
|
+
- predefined validators:
|
804
|
+
- `:not_nil`
|
805
|
+
- `:integer`
|
806
|
+
- `:float`
|
807
|
+
- `:numeric`
|
808
|
+
- `:big_decimal`
|
809
|
+
- `:array`
|
810
|
+
- `:hash`
|
811
|
+
- `:string`
|
812
|
+
- `:symbol`
|
813
|
+
- `:text` (`string` or `symbol`)
|
814
|
+
- `:boolean`
|
815
|
+
- `:class`
|
816
|
+
- `:module`
|
817
|
+
- `:proc`
|
818
|
+
|
819
|
+
```ruby
|
820
|
+
class Config < Qonfig::DataSet
|
821
|
+
setting :user
|
822
|
+
setting :password
|
823
|
+
|
824
|
+
setting :service do
|
825
|
+
setting :provider
|
826
|
+
setting :protocol
|
827
|
+
setting :on_fail, -> { puts 'atata!' }
|
828
|
+
end
|
829
|
+
|
830
|
+
setting :ignorance, false
|
831
|
+
|
832
|
+
validate 'user', :string
|
833
|
+
validate 'password', :string
|
834
|
+
validate 'service.provider', :text
|
835
|
+
validate 'service.protocol', :text
|
836
|
+
validate 'service.on_fail', :proc
|
837
|
+
validate 'ignorance', :not_nil
|
838
|
+
end
|
839
|
+
|
840
|
+
config = Config.new do |conf|
|
841
|
+
conf.user = 'D@iVeR'
|
842
|
+
conf.password = 'test123'
|
843
|
+
conf.service.provider = :google
|
844
|
+
conf.service.protocol = :https
|
845
|
+
end # NOTE: all right :)
|
846
|
+
|
847
|
+
config.settings.ignorance = nil # => Qonfig::ValidationError (cant be nil)
|
848
|
+
```
|
849
|
+
|
850
|
+
---
|
851
|
+
|
852
|
+
## Work with files
|
853
|
+
|
426
854
|
### Load from YAML file
|
427
855
|
|
428
856
|
- supports `ERB`;
|
@@ -922,116 +1350,6 @@ dynamic: 10
|
|
922
1350
|
|
923
1351
|
---
|
924
1352
|
|
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
1353
|
### Plugins
|
1036
1354
|
|
1037
1355
|
```ruby
|
@@ -1047,23 +1365,25 @@ Qonfig.plugin(:plugin_name) # or Qonfig.plugin('plugin_name')
|
|
1047
1365
|
### Plugins: toml
|
1048
1366
|
|
1049
1367
|
- adds support for `toml` format ([specification](https://github.com/toml-lang/toml));
|
1050
|
-
- depends on `toml-rb` gem
|
1051
|
-
-
|
1052
|
-
- provides `
|
1053
|
-
- provides `
|
1368
|
+
- depends on `toml-rb` gem ([link](https://github.com/emancu/toml-rb));
|
1369
|
+
- supports TOML `0.4.0` format (dependency lock);
|
1370
|
+
- provides `load_from_toml` (works in `load_from_yaml` manner ([doc](#load-from-yaml-file)));
|
1371
|
+
- provides `save_to_toml` (works in `save_to_yaml` manner ([doc](#save-to-yaml-file))) (`toml-rb` has no native options);
|
1372
|
+
- provides `expose_toml` (works in `expose_yaml` manner ([doc](#expose-yaml)));
|
1054
1373
|
|
1055
1374
|
```ruby
|
1375
|
+
# 1) require external dependency
|
1056
1376
|
require 'toml-rb'
|
1377
|
+
|
1378
|
+
# 2) enable plugin
|
1057
1379
|
Qonfig.plugin(:toml)
|
1058
|
-
|
1380
|
+
|
1381
|
+
# 3) use :)
|
1059
1382
|
```
|
1060
1383
|
---
|
1061
1384
|
|
1062
1385
|
## Roadmap
|
1063
1386
|
|
1064
|
-
- support for TOML format;
|
1065
|
-
- explicit "settings" object;
|
1066
|
-
- validation layer;
|
1067
1387
|
- distributed configuration server;
|
1068
1388
|
- support for Rails-like secrets;
|
1069
1389
|
|
@@ -10,6 +10,7 @@ module Qonfig
|
|
10
10
|
require_relative 'qonfig/uploaders'
|
11
11
|
require_relative 'qonfig/commands'
|
12
12
|
require_relative 'qonfig/command_set'
|
13
|
+
require_relative 'qonfig/validator'
|
13
14
|
require_relative 'qonfig/settings'
|
14
15
|
require_relative 'qonfig/dsl'
|
15
16
|
require_relative 'qonfig/data_set'
|
@@ -3,12 +3,18 @@
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
5
|
class Qonfig::CommandSet
|
6
|
+
# @api private
|
7
|
+
# @since 0.13.0
|
8
|
+
include Enumerable
|
9
|
+
|
6
10
|
# @return [Array<Qonfig::Commands::Base>]
|
7
11
|
#
|
8
12
|
# @api private
|
9
13
|
# @since 0.1.0
|
10
14
|
attr_reader :commands
|
11
15
|
|
16
|
+
# @return [void]
|
17
|
+
#
|
12
18
|
# @api private
|
13
19
|
# @since 0.1.0
|
14
20
|
def initialize
|
@@ -57,7 +63,7 @@ class Qonfig::CommandSet
|
|
57
63
|
private
|
58
64
|
|
59
65
|
# @param block [Proc]
|
60
|
-
# @return [
|
66
|
+
# @return [Any]
|
61
67
|
#
|
62
68
|
# @api private
|
63
69
|
# @since 0.2.0
|
@@ -32,14 +32,16 @@ class Qonfig::Commands::AddNestedOption < Qonfig::Commands::Base
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
# @param data_set [Qonfig::DataSet]
|
35
36
|
# @param settings [Qonfig::Settings]
|
36
37
|
# @return [void]
|
37
38
|
#
|
38
39
|
# @api private
|
39
40
|
# @since 0.1.0
|
40
|
-
def call(settings)
|
41
|
+
def call(data_set, settings)
|
41
42
|
nested_settings = nested_data_set_klass.new.settings
|
42
43
|
|
44
|
+
nested_settings.__mutation_callbacks__.add(settings.__mutation_callbacks__)
|
43
45
|
settings.__define_setting__(key, nested_settings)
|
44
46
|
end
|
45
47
|
end
|
@@ -30,12 +30,13 @@ class Qonfig::Commands::AddOption < Qonfig::Commands::Base
|
|
30
30
|
@value = value
|
31
31
|
end
|
32
32
|
|
33
|
+
# @param data_set [Qonfig::DataSet]
|
33
34
|
# @param settings [Qonfig::Settings]
|
34
35
|
# @return [void]
|
35
36
|
#
|
36
37
|
# @api private
|
37
38
|
# @since 0.1.0
|
38
|
-
def call(settings)
|
39
|
+
def call(data_set, settings)
|
39
40
|
settings.__define_setting__(key, value)
|
40
41
|
end
|
41
42
|
end
|
@@ -3,10 +3,11 @@
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
5
|
class Qonfig::Commands::Base
|
6
|
+
# @param data_set [Qonfig::DataSet]
|
6
7
|
# @param settings [Qonfig::Settings]
|
7
8
|
# @return [void]
|
8
9
|
#
|
9
10
|
# @api private
|
10
11
|
# @since 0.1.0
|
11
|
-
def call(settings); end
|
12
|
+
def call(data_set, settings); end
|
12
13
|
end
|
@@ -7,31 +7,37 @@ class Qonfig::Commands::Compose < Qonfig::Commands::Base
|
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
# @since 0.1.0
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :composable_data_set_klass
|
11
11
|
|
12
|
-
# @param
|
12
|
+
# @param composable_data_set_klass [Qonfig::DataSet]
|
13
13
|
#
|
14
14
|
# @raise [Qonfig::ArgumentError]
|
15
15
|
#
|
16
16
|
# @api private
|
17
17
|
# @since 0.1.0
|
18
|
-
def initialize(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
def initialize(composable_data_set_klass)
|
19
|
+
unless composable_data_set_klass.is_a?(Class) && composable_data_set_klass < Qonfig::DataSet
|
20
|
+
raise(
|
21
|
+
Qonfig::ArgumentError,
|
22
|
+
'Composed config class should be a subtype of Qonfig::DataSet'
|
23
|
+
)
|
24
|
+
end
|
23
25
|
|
24
|
-
@
|
26
|
+
@composable_data_set_klass = composable_data_set_klass
|
25
27
|
end
|
26
28
|
|
29
|
+
# @param data_set [Qonfig::DataSet]
|
27
30
|
# @param settings [Qonfig::Settings]
|
28
31
|
# @return [void]
|
29
32
|
#
|
30
33
|
# @api private
|
31
34
|
# @since 0.1.0
|
32
|
-
def call(settings)
|
33
|
-
|
35
|
+
def call(data_set, settings)
|
36
|
+
# NOTE: append new validators
|
37
|
+
data_set.class.validators.concat(composable_data_set_klass.validators.dup)
|
34
38
|
|
39
|
+
# NOTE: append new settings
|
40
|
+
composite_settings = composable_data_set_klass.new.settings
|
35
41
|
settings.__append_settings__(composite_settings)
|
36
42
|
end
|
37
43
|
end
|
@@ -60,12 +60,13 @@ class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
|
|
60
60
|
@env = env
|
61
61
|
end
|
62
62
|
|
63
|
+
# @param data_set [Qonfig::DataSet]
|
63
64
|
# @param settings [Qonfig::Settings]
|
64
65
|
# @return [void]
|
65
66
|
#
|
66
67
|
# @api private
|
67
68
|
# @since 0.7.0
|
68
|
-
def call(settings)
|
69
|
+
def call(data_set, settings)
|
69
70
|
case via
|
70
71
|
when EXPOSERS[:file_name]
|
71
72
|
expose_file_name!(settings)
|
@@ -55,12 +55,13 @@ class Qonfig::Commands::LoadFromENV < Qonfig::Commands::Base
|
|
55
55
|
@trim_pattern = prefix.is_a?(Regexp) ? prefix : /\A(#{Regexp.escape(prefix.to_s)})/m
|
56
56
|
end
|
57
57
|
|
58
|
+
# @param data_set [Qonfig::DataSet]
|
58
59
|
# @param settings [Qonfig::Settings]
|
59
60
|
# @return [void]
|
60
61
|
#
|
61
62
|
# @api private
|
62
63
|
# @since 0.2.0
|
63
|
-
def call(settings)
|
64
|
+
def call(data_set, settings)
|
64
65
|
env_data = extract_env_data
|
65
66
|
|
66
67
|
env_based_settings = build_data_set_class(env_data).new.settings
|
@@ -25,12 +25,13 @@ class Qonfig::Commands::LoadFromJSON < Qonfig::Commands::Base
|
|
25
25
|
@strict = strict
|
26
26
|
end
|
27
27
|
|
28
|
+
# @param data_set [Qonfig::DataSet]
|
28
29
|
# @param settings [Qonfig::Settings]
|
29
30
|
# @return [void]
|
30
31
|
#
|
31
32
|
# @api private
|
32
33
|
# @since 0.5.0
|
33
|
-
def call(settings)
|
34
|
+
def call(data_set, settings)
|
34
35
|
json_data = Qonfig::Loaders::JSON.load_file(file_path, fail_on_unexist: strict)
|
35
36
|
|
36
37
|
raise(
|
@@ -17,12 +17,13 @@ class Qonfig::Commands::LoadFromSelf < Qonfig::Commands::Base
|
|
17
17
|
@caller_location = caller_location
|
18
18
|
end
|
19
19
|
|
20
|
+
# @param data_set [Qonfig::DataSet]
|
20
21
|
# @param settings [Qonfig::Settings]
|
21
22
|
# @return [void]
|
22
23
|
#
|
23
24
|
# @api private
|
24
25
|
# @since 0.2.0
|
25
|
-
def call(settings)
|
26
|
+
def call(data_set, settings)
|
26
27
|
yaml_data = load_self_placed_yaml_data
|
27
28
|
|
28
29
|
yaml_based_settings = build_data_set_klass(yaml_data).new.settings
|
@@ -25,6 +25,7 @@ class Qonfig::Commands::LoadFromYAML < Qonfig::Commands::Base
|
|
25
25
|
@strict = strict
|
26
26
|
end
|
27
27
|
|
28
|
+
# @param data_set [Qonfig::DataSet]
|
28
29
|
# @param settings [Qonfig::Settings]
|
29
30
|
# @return [void]
|
30
31
|
#
|
@@ -32,7 +33,7 @@ class Qonfig::Commands::LoadFromYAML < Qonfig::Commands::Base
|
|
32
33
|
#
|
33
34
|
# @api private
|
34
35
|
# @since 0.2.0
|
35
|
-
def call(settings)
|
36
|
+
def call(data_set, settings)
|
36
37
|
yaml_data = Qonfig::Loaders::YAML.load_file(file_path, fail_on_unexist: strict)
|
37
38
|
|
38
39
|
raise(
|
@@ -2,29 +2,30 @@
|
|
2
2
|
|
3
3
|
# @api public
|
4
4
|
# @since 0.1.0
|
5
|
-
class Qonfig::DataSet
|
5
|
+
class Qonfig::DataSet # rubocop:disable Metrics/ClassLength
|
6
6
|
require_relative 'data_set/class_builder'
|
7
|
-
require_relative 'data_set/
|
7
|
+
require_relative 'data_set/lock'
|
8
8
|
|
9
9
|
# @since 0.1.0
|
10
10
|
extend Qonfig::DSL
|
11
11
|
|
12
|
+
# @since 0.13.0
|
13
|
+
extend Qonfig::Validator::DSL
|
14
|
+
|
12
15
|
# @return [Qonfig::Settings]
|
13
16
|
#
|
14
17
|
# @api private
|
15
18
|
# @since 0.1.0
|
16
19
|
attr_reader :settings
|
17
20
|
|
18
|
-
# @param
|
21
|
+
# @param settings_map [Hash]
|
19
22
|
# @param configurations [Proc]
|
20
23
|
#
|
21
24
|
# @api public
|
22
25
|
# @since 0.1.0
|
23
|
-
def initialize(
|
24
|
-
@
|
25
|
-
|
26
|
+
def initialize(settings_map = {}, &configurations)
|
27
|
+
@__lock__ = Qonfig::DataSet::Lock.new
|
28
|
+
thread_safe_definition { load!(settings_map, &configurations) }
|
26
|
-
|
27
|
-
thread_safe_definition { load!(options_map, &configurations) }
|
28
29
|
end
|
29
30
|
|
30
31
|
# @return [void]
|
@@ -43,7 +44,7 @@ class Qonfig::DataSet
|
|
43
44
|
thread_safe_access { settings.__is_frozen__ }
|
44
45
|
end
|
45
46
|
|
46
|
-
# @param
|
47
|
+
# @param settings_map [Hash]
|
47
48
|
# @param configurations [Proc]
|
48
49
|
# @return [void]
|
49
50
|
#
|
@@ -51,22 +52,21 @@ class Qonfig::DataSet
|
|
51
52
|
#
|
52
53
|
# @api public
|
53
54
|
# @since 0.2.0
|
54
|
-
def reload!(
|
55
|
+
def reload!(settings_map = {}, &configurations)
|
55
56
|
thread_safe_definition do
|
56
57
|
raise Qonfig::FrozenSettingsError, 'Frozen config can not be reloaded' if frozen?
|
57
|
-
load!(
|
58
|
+
load!(settings_map, &configurations)
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
61
|
-
# @param
|
62
|
+
# @param settings_map [Hash]
|
62
63
|
# @return [void]
|
63
64
|
#
|
64
65
|
# @api public
|
65
66
|
# @since 0.1.0
|
66
|
-
def configure(
|
67
|
+
def configure(settings_map = {}, &configurations)
|
67
68
|
thread_safe_access do
|
68
|
-
|
69
|
+
apply_settings(settings_map, &configurations)
|
69
|
-
yield(settings) if block_given?
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -172,25 +172,94 @@ class Qonfig::DataSet
|
|
172
172
|
thread_safe_access { settings.__clear__ }
|
173
173
|
end
|
174
174
|
|
175
|
+
# @param block [Proc]
|
176
|
+
# @return [Enumerable]
|
177
|
+
#
|
178
|
+
# @yield [setting_key, setting_value]
|
179
|
+
# @yieldparam setting_key [String]
|
180
|
+
# @yieldparam setting_value [Object]
|
181
|
+
#
|
182
|
+
# @api public
|
183
|
+
# @since 0.13.0
|
184
|
+
def each_setting(&block)
|
185
|
+
thread_safe_access { settings.__each_setting__(&block) }
|
186
|
+
end
|
187
|
+
|
188
|
+
# @param block [Proc]
|
189
|
+
# @return [Enumerable]
|
190
|
+
#
|
191
|
+
# @yield [setting_key, setting_value]
|
192
|
+
# @yieldparam setting_key [String]
|
193
|
+
# @yieldparam setting_value [Object]
|
194
|
+
#
|
195
|
+
# @api public
|
196
|
+
# @since 0.13.0
|
197
|
+
def deep_each_setting(&block)
|
198
|
+
thread_safe_access { settings.__deep_each_setting__(&block) }
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [Boolean]
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
# @since 0.13.0
|
205
|
+
def valid?
|
206
|
+
thread_safe_access { validator.valid? }
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return [void]
|
210
|
+
#
|
211
|
+
# @api public
|
212
|
+
# @since 0.13.0
|
213
|
+
def validate!
|
214
|
+
thread_safe_access { validator.validate! }
|
215
|
+
end
|
216
|
+
|
175
217
|
private
|
176
218
|
|
177
|
-
# @return [Qonfig::
|
219
|
+
# @return [Qonfig::Validator]
|
220
|
+
#
|
221
|
+
# @api private
|
222
|
+
# @since 0.13.0
|
223
|
+
attr_reader :validator
|
224
|
+
|
225
|
+
# @return [void]
|
178
226
|
#
|
179
227
|
# @api private
|
180
228
|
# @since 0.2.0
|
181
229
|
def build_settings
|
182
|
-
Qonfig::Settings::Builder.build(self
|
230
|
+
@settings = Qonfig::Settings::Builder.build(self)
|
231
|
+
validator.validate!
|
232
|
+
end
|
233
|
+
|
234
|
+
# @return [void]
|
235
|
+
#
|
236
|
+
# @api private
|
237
|
+
# @since 0.13.0
|
238
|
+
def build_validator
|
239
|
+
@validator = Qonfig::Validator.new(self)
|
240
|
+
end
|
241
|
+
|
242
|
+
# @param settings_map [Hash]
|
243
|
+
# @param configurations [Proc]
|
244
|
+
# @return [void]
|
245
|
+
#
|
246
|
+
# @api private
|
247
|
+
# @since 0.13.0
|
248
|
+
def apply_settings(settings_map = {}, &configurations)
|
249
|
+
settings.__apply_values__(settings_map)
|
250
|
+
yield(settings) if block_given?
|
183
251
|
end
|
184
252
|
|
185
|
-
# @param
|
253
|
+
# @param settings_map [Hash]
|
186
254
|
# @param configurations [Proc]
|
187
255
|
# @return [void]
|
188
256
|
#
|
189
257
|
# @api private
|
190
258
|
# @since 0.2.0
|
191
|
-
def load!(
|
192
|
-
|
193
|
-
|
259
|
+
def load!(settings_map = {}, &configurations)
|
260
|
+
build_validator
|
261
|
+
build_settings
|
262
|
+
apply_settings(settings_map, &configurations)
|
194
263
|
end
|
195
264
|
|
196
265
|
# @param instructions [Proc]
|
@@ -199,7 +268,7 @@ class Qonfig::DataSet
|
|
199
268
|
# @api private
|
200
269
|
# @since 0.2.0
|
201
270
|
def thread_safe_access(&instructions)
|
202
|
-
@
|
271
|
+
@__lock__.thread_safe_access(&instructions)
|
203
272
|
end
|
204
273
|
|
205
274
|
# @param instructions [Proc]
|
@@ -208,6 +277,6 @@ class Qonfig::DataSet
|
|
208
277
|
# @api private
|
209
278
|
# @since 0.2.0
|
210
279
|
def thread_safe_definition(&instructions)
|
211
|
-
@
|
280
|
+
@__lock__.thread_safe_definition(&instructions)
|
212
281
|
end
|
213
282
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::DataSet::Lock
|
6
|
+
# @return [void]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
def initialize
|
11
|
+
@access_lock = Mutex.new
|
12
|
+
@definition_lock = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param instructions [Proc]
|
16
|
+
# @return [void]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @since 0.13.0
|
20
|
+
def thread_safe_access(&instructions)
|
21
|
+
access_lock.owned? ? yield : access_lock.synchronize(&instructions)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param instructions [Proc]
|
25
|
+
# @return [void]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
# @since 0.13.0
|
29
|
+
def thread_safe_definition(&instructions)
|
30
|
+
definition_lock.owned? ? yield : definition_lock.synchronize(&instructions)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @return [Mutex]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
# @since 0.13.0
|
39
|
+
attr_reader :access_lock
|
40
|
+
|
41
|
+
# @return [Mutex]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
# @since 0.13.0
|
45
|
+
attr_reader :definition_lock
|
46
|
+
end
|
@@ -1,7 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# @api public
|
4
|
-
# @since 0.?.0
|
5
|
-
class Qonfig::DataSet::Validator
|
6
|
-
# TODO: think about validation layer
|
7
|
-
end
|
@@ -10,6 +10,14 @@ module Qonfig
|
|
10
10
|
ArgumentError = Class.new(ArgumentError)
|
11
11
|
|
12
12
|
# @api public
|
13
|
+
# @since 0.13.0
|
14
|
+
ValidatorArgumentError = Class.new(ArgumentError)
|
15
|
+
|
16
|
+
# @api public
|
17
|
+
# @since 0.13.0
|
18
|
+
ValidationError = Class.new(Error)
|
19
|
+
|
20
|
+
# @api public
|
13
21
|
# @since 0.12.0
|
14
22
|
PluginError = Class.new(Error)
|
15
23
|
|
@@ -60,12 +60,13 @@ class Qonfig::Commands::ExposeTOML < Qonfig::Commands::Base
|
|
60
60
|
@env = env
|
61
61
|
end
|
62
62
|
|
63
|
+
# @param data_set [Qonfig::DataSet]
|
63
64
|
# @param settings [Qonfig::Settings]
|
64
65
|
# @return [void]
|
65
66
|
#
|
66
67
|
# @api private
|
67
68
|
# @since 0.12.0
|
68
|
-
def call(settings)
|
69
|
+
def call(data_set, settings)
|
69
70
|
case via
|
70
71
|
when EXPOSERS[:file_name]
|
71
72
|
expose_file_name!(settings)
|
@@ -25,12 +25,13 @@ class Qonfig::Commands::LoadFromTOML < Qonfig::Commands::Base
|
|
25
25
|
@strict = strict
|
26
26
|
end
|
27
27
|
|
28
|
+
# @param data_set [Qonfig::DataSet]
|
28
29
|
# @param settings [Qonfig::Settings]
|
29
30
|
# @return [void]
|
30
31
|
#
|
31
32
|
# @api private
|
32
33
|
# @since 0.12.0
|
33
|
-
def call(settings)
|
34
|
+
def call(data_set, settings)
|
34
35
|
toml_data = Qonfig::Loaders::TOML.load_file(file_path, fail_on_unexist: strict)
|
35
36
|
toml_based_settings = build_data_set_class(toml_data).new.settings
|
36
37
|
settings.__append_settings__(toml_based_settings)
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
|
-
# rubocop:disable Metrics/ClassLength
|
6
|
-
class Qonfig::Settings
|
5
|
+
# rubocop:disable Metrics/ClassLength, Layout/ClassStructure
|
6
|
+
class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_METHODS constant
|
7
|
+
require_relative 'settings/callbacks'
|
7
8
|
require_relative 'settings/lock'
|
8
9
|
require_relative 'settings/builder'
|
9
10
|
require_relative 'settings/key_guard'
|
11
|
+
require_relative 'settings/key_matcher'
|
10
12
|
|
11
13
|
# @return [Proc]
|
12
14
|
#
|
@@ -26,11 +28,49 @@ class Qonfig::Settings
|
|
26
28
|
# @since 0.1.0
|
27
29
|
attr_reader :__options__
|
28
30
|
|
31
|
+
# @return [Qonfig::Settings::Callbacks]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
# @since 0.13.0
|
35
|
+
attr_reader :__mutation_callbacks__
|
36
|
+
|
29
37
|
# @api private
|
30
38
|
# @since 0.1.0
|
31
|
-
def initialize
|
39
|
+
def initialize(__mutation_callbacks__)
|
32
40
|
@__options__ = {}
|
33
41
|
@__lock__ = Lock.new
|
42
|
+
@__mutation_callbacks__ = __mutation_callbacks__
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param block [Proc]
|
46
|
+
# @return [Enumerable]
|
47
|
+
#
|
48
|
+
# @yield [key, value]
|
49
|
+
# @yieldparam key [String]
|
50
|
+
# @yieldparam value [Object]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
# @since 0.13.0
|
54
|
+
def __each_setting__(&block)
|
55
|
+
__lock__.thread_safe_access do
|
56
|
+
__each_key_value_pair__(&block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param initial_setting_key [String, NilClass]
|
61
|
+
# @param block [Proc]
|
62
|
+
# @return [Enumerable]
|
63
|
+
#
|
64
|
+
# @yield [key, value]
|
65
|
+
# @yieldparam key [String]
|
66
|
+
# @yieldparam value [Object]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
# @since 0.13.0
|
70
|
+
def __deep_each_setting__(initial_setting_key = nil, &block)
|
71
|
+
__lock__.thread_safe_access do
|
72
|
+
__deep_each_key_value_pair__(initial_setting_key, &block)
|
73
|
+
end
|
34
74
|
end
|
35
75
|
|
36
76
|
# @param key [Symbol, String]
|
@@ -39,7 +79,7 @@ class Qonfig::Settings
|
|
39
79
|
#
|
40
80
|
# @api private
|
41
81
|
# @since 0.1.0
|
42
|
-
def __define_setting__(key, value)
|
82
|
+
def __define_setting__(key, value) # rubocop:disable Metrics/AbcSize
|
43
83
|
__lock__.thread_safe_definition do
|
44
84
|
key = __indifferently_accessable_option_key__(key)
|
45
85
|
|
@@ -48,13 +88,15 @@ class Qonfig::Settings
|
|
48
88
|
case
|
49
89
|
when !__options__.key?(key)
|
50
90
|
__options__[key] = value
|
51
|
-
when __options__[key
|
91
|
+
when __is_a_setting__(__options__[key]) && __is_a_setting__(value)
|
52
92
|
__options__[key].__append_settings__(value)
|
53
93
|
else
|
54
94
|
__options__[key] = value
|
55
95
|
end
|
56
96
|
|
57
|
-
|
97
|
+
__define_option_reader__(key)
|
98
|
+
__define_option_writer__(key)
|
99
|
+
__define_option_predicate__(key)
|
58
100
|
end
|
59
101
|
end
|
60
102
|
|
@@ -71,6 +113,14 @@ class Qonfig::Settings
|
|
71
113
|
end
|
72
114
|
end
|
73
115
|
|
116
|
+
# @return [void]
|
117
|
+
#
|
118
|
+
# @api private
|
119
|
+
# @since 0.13.0
|
120
|
+
def __invoke_mutation_callbacks__
|
121
|
+
__mutation_callbacks__.call
|
122
|
+
end
|
123
|
+
|
74
124
|
# @param key [Symbol, String]
|
75
125
|
# @return [Object]
|
76
126
|
#
|
@@ -90,13 +140,13 @@ class Qonfig::Settings
|
|
90
140
|
__lock__.thread_safe_access { __set_value__(key, value) }
|
91
141
|
end
|
92
142
|
|
93
|
-
# @param
|
143
|
+
# @param settings_map [Hash]
|
94
144
|
# @return [void]
|
95
145
|
#
|
96
146
|
# @api private
|
97
147
|
# @since 0.3.0
|
98
|
-
def __apply_values__(
|
99
|
-
__lock__.thread_safe_access { __set_values_from_map__(
|
148
|
+
def __apply_values__(settings_map)
|
149
|
+
__lock__.thread_safe_access { __set_values_from_map__(settings_map) }
|
100
150
|
end
|
101
151
|
|
102
152
|
# @param keys [Array<String, Symbol>]
|
@@ -191,7 +241,7 @@ class Qonfig::Settings
|
|
191
241
|
__options__.freeze
|
192
242
|
|
193
243
|
__options__.each_value do |value|
|
194
|
-
value.__freeze__ if value
|
244
|
+
value.__freeze__ if __is_a_setting__(value)
|
195
245
|
end
|
196
246
|
end
|
197
247
|
end
|
@@ -204,6 +254,15 @@ class Qonfig::Settings
|
|
204
254
|
__lock__.thread_safe_access { __options__.frozen? }
|
205
255
|
end
|
206
256
|
|
257
|
+
# @param value [Any]
|
258
|
+
# @return [Boolean]
|
259
|
+
#
|
260
|
+
# @api private
|
261
|
+
# @since 0.13.0
|
262
|
+
def __is_a_setting__(value)
|
263
|
+
value.is_a?(Qonfig::Settings)
|
264
|
+
end
|
265
|
+
|
207
266
|
private
|
208
267
|
|
209
268
|
# @return [Qonfig::Settings::Lock]
|
@@ -212,7 +271,47 @@ class Qonfig::Settings
|
|
212
271
|
# @since 0.2.0
|
213
272
|
attr_reader :__lock__
|
214
273
|
|
215
|
-
# @param
|
274
|
+
# @param block [Proc]
|
275
|
+
# @return [Enumerable]
|
276
|
+
#
|
277
|
+
# @yield [setting_key, setting_value]
|
278
|
+
# @yieldparam key [String]
|
279
|
+
# @yieldparam value [Object]
|
280
|
+
#
|
281
|
+
# @api private
|
282
|
+
# @since 0.13.0
|
283
|
+
def __each_key_value_pair__(&block)
|
284
|
+
__options__.each_pair(&block)
|
285
|
+
end
|
286
|
+
|
287
|
+
# @param initial_setting_key [String, NilClass]
|
288
|
+
# @param block [Proc]
|
289
|
+
# @return [Enumerable]
|
290
|
+
#
|
291
|
+
# @yield [setting_key, setting_value]
|
292
|
+
# @yieldparam setting_key [String]
|
293
|
+
# @yieldparam setting_value [Object]
|
294
|
+
#
|
295
|
+
# @api private
|
296
|
+
# @since 0.13.0
|
297
|
+
def __deep_each_key_value_pair__(initial_setting_key = nil, &block)
|
298
|
+
enumerator = Enumerator.new do |yielder|
|
299
|
+
__each_key_value_pair__ do |setting_key, setting_value|
|
300
|
+
final_setting_key =
|
301
|
+
initial_setting_key ? "#{initial_setting_key}.#{setting_key}" : setting_key
|
302
|
+
|
303
|
+
if __is_a_setting__(setting_value)
|
304
|
+
setting_value.__deep_each_setting__(final_setting_key, &block)
|
305
|
+
else
|
306
|
+
yielder.yield(final_setting_key, setting_value)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
block_given? ? enumerator.each(&block) : enumerator
|
312
|
+
end
|
313
|
+
|
314
|
+
# @param settings_map [Hash]
|
216
315
|
# @return [void]
|
217
316
|
#
|
218
317
|
# @raise [Qonfig::ArgumentError]
|
@@ -220,21 +319,21 @@ class Qonfig::Settings
|
|
220
319
|
#
|
221
320
|
# @api private
|
222
321
|
# @since 0.3.0
|
223
|
-
def __set_values_from_map__(
|
322
|
+
def __set_values_from_map__(settings_map)
|
224
323
|
::Kernel.raise(
|
225
324
|
Qonfig::ArgumentError, 'Options map should be represented as a hash'
|
226
|
-
) unless
|
325
|
+
) unless settings_map.is_a?(Hash)
|
227
326
|
|
228
|
-
|
327
|
+
settings_map.each_pair do |key, value|
|
229
328
|
current_value = __get_value__(key)
|
230
329
|
|
231
330
|
# NOTE: some duplications here was made only for the better code readability
|
232
331
|
case
|
233
|
-
when !current_value
|
332
|
+
when !__is_a_setting__(current_value)
|
234
333
|
__set_value__(key, value)
|
235
|
-
when current_value
|
334
|
+
when __is_a_setting__(current_value) && value.is_a?(Hash)
|
236
335
|
current_value.__apply_values__(value)
|
237
|
-
when current_value
|
336
|
+
when __is_a_setting__(current_value) && !value.is_a?(Hash)
|
238
337
|
::Kernel.raise(
|
239
338
|
Qonfig::AmbiguousSettingValueError,
|
240
339
|
"Can not redefine option <#{key}> that contains nested options"
|
@@ -255,12 +354,10 @@ class Qonfig::Settings
|
|
255
354
|
) if __options__.frozen?
|
256
355
|
|
257
356
|
__options__.each_pair do |key, value|
|
258
|
-
|
357
|
+
__is_a_setting__(value) ? value.__clear__ : __options__[key] = nil
|
259
|
-
value.__clear__
|
260
|
-
else
|
261
|
-
__options__[key] = nil
|
262
|
-
end
|
263
358
|
end
|
359
|
+
|
360
|
+
__invoke_mutation_callbacks__
|
264
361
|
end
|
265
362
|
|
266
363
|
# @param key [String, Symbol]
|
@@ -301,14 +398,16 @@ class Qonfig::Settings
|
|
301
398
|
::Kernel.raise(Qonfig::FrozenSettingsError, 'Can not modify frozen settings')
|
302
399
|
end
|
303
400
|
|
304
|
-
if __options__[key
|
401
|
+
if __is_a_setting__(__options__[key])
|
305
402
|
::Kernel.raise(
|
306
403
|
Qonfig::AmbiguousSettingValueError,
|
307
404
|
"Can not redefine option <#{key}> that contains nested options"
|
308
405
|
)
|
309
406
|
end
|
310
407
|
|
311
|
-
__options__[key] = value
|
408
|
+
(__options__[key] = value)
|
409
|
+
|
410
|
+
__invoke_mutation_callbacks__
|
312
411
|
end
|
313
412
|
|
314
413
|
# @param keys [Array<Symbol, String>]
|
@@ -328,12 +427,12 @@ class Qonfig::Settings
|
|
328
427
|
case
|
329
428
|
when rest_keys.empty?
|
330
429
|
result
|
331
|
-
when !result
|
430
|
+
when !__is_a_setting__(result)
|
332
431
|
::Kernel.raise(
|
333
432
|
Qonfig::UnknownSettingError,
|
334
433
|
'Setting with required digging sequence does not exist!'
|
335
434
|
)
|
336
|
-
when result
|
435
|
+
when __is_a_setting__(result)
|
337
436
|
result.__dig__(*rest_keys)
|
338
437
|
end
|
339
438
|
end
|
@@ -350,7 +449,7 @@ class Qonfig::Settings
|
|
350
449
|
{}.tap do |result|
|
351
450
|
__deep_access__(*keys).tap do |setting|
|
352
451
|
required_key = __indifferently_accessable_option_key__(keys.last)
|
353
|
-
result[required_key] = setting
|
452
|
+
result[required_key] = __is_a_setting__(setting) ? setting.__to_h__ : setting
|
354
453
|
end
|
355
454
|
end
|
356
455
|
end
|
@@ -386,7 +485,7 @@ class Qonfig::Settings
|
|
386
485
|
transform_key: transform_key,
|
387
486
|
transform_value: transform_value
|
388
487
|
)
|
389
|
-
when value
|
488
|
+
when __is_a_setting__(value)
|
390
489
|
hash[final_key] = value.__to_hash__(
|
391
490
|
transform_key: transform_key,
|
392
491
|
transform_value: transform_value
|
@@ -402,16 +501,30 @@ class Qonfig::Settings
|
|
402
501
|
# @return [void]
|
403
502
|
#
|
404
503
|
# @api private
|
405
|
-
# @since 0.
|
406
|
-
def
|
504
|
+
# @since 0.13.0
|
505
|
+
def __define_option_reader__(key)
|
407
506
|
define_singleton_method(key) do
|
408
507
|
self.[](key)
|
409
508
|
end
|
509
|
+
end
|
410
510
|
|
511
|
+
# @param key [Symbol, String]
|
512
|
+
# @return [void]
|
513
|
+
#
|
514
|
+
# @api private
|
515
|
+
# @since 0.13.0
|
516
|
+
def __define_option_writer__(key)
|
411
517
|
define_singleton_method("#{key}=") do |value|
|
412
518
|
self.[]=(key, value)
|
413
519
|
end
|
520
|
+
end
|
414
521
|
|
522
|
+
# @param key [Symbol, String]
|
523
|
+
# @return [void]
|
524
|
+
#
|
525
|
+
# @api private
|
526
|
+
# @since 0.13.0
|
527
|
+
def __define_option_predicate__(key)
|
415
528
|
define_singleton_method("#{key}?") do
|
416
529
|
!!self.[](key)
|
417
530
|
end
|
@@ -442,7 +555,6 @@ class Qonfig::Settings
|
|
442
555
|
KeyGuard.new(key).prevent_core_method_intersection!
|
443
556
|
end
|
444
557
|
|
445
|
-
# rubocop:disable Layout/ClassStructure
|
446
558
|
# @return [Array<String>]
|
447
559
|
#
|
448
560
|
# @api private
|
@@ -452,6 +564,5 @@ class Qonfig::Settings
|
|
452
564
|
private_instance_methods(false) |
|
453
565
|
%i[super define_singleton_method self]
|
454
566
|
).map(&:to_s).freeze
|
455
|
-
# rubocop:enable Layout/ClassStructure
|
456
567
|
end
|
457
|
-
# rubocop:enable Metrics/ClassLength
|
568
|
+
# rubocop:enable Metrics/ClassLength, Layout/ClassStructure
|
@@ -4,14 +4,30 @@
|
|
4
4
|
# @since 0.2.0
|
5
5
|
module Qonfig::Settings::Builder
|
6
6
|
class << self
|
7
|
-
# @param
|
7
|
+
# @param data_set [Qonfig::DataSet]
|
8
8
|
# @return [Qonfig::Settings]
|
9
9
|
#
|
10
10
|
# @api private
|
11
11
|
# @since 0.2.0
|
12
|
-
def build(
|
13
|
-
Qonfig::Settings.new
|
14
|
-
commands.each
|
12
|
+
def build(data_set)
|
13
|
+
Qonfig::Settings.new(build_mutation_callbacks(data_set)).tap do |settings|
|
14
|
+
data_set.class.commands.dup.each do |command|
|
15
|
+
command.call(data_set, settings)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# @param data_set [Qonfig::DataSet]
|
23
|
+
# @return [Qonfig::Settings::Callbacks]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
# @since 0.13.0
|
27
|
+
def build_mutation_callbacks(data_set)
|
28
|
+
Qonfig::Settings::Callbacks.new.tap do |callbacks|
|
29
|
+
# NOTE: validation callbacks
|
30
|
+
callbacks.add(proc { data_set.validate! })
|
15
31
|
end
|
16
32
|
end
|
17
33
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Settings::Callbacks
|
6
|
+
# @api private
|
7
|
+
# @since 0.13.0
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# @return [void]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 0.13.0
|
14
|
+
def initialize
|
15
|
+
@callbacks = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [void]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
# @since 0.13.0
|
22
|
+
def call
|
23
|
+
callbacks.each(&:call)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param callback [Proc, Qonfig::Settings::Callbacks, #call]
|
27
|
+
# @return [void]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
# @since 0.13.0
|
31
|
+
def add(callback)
|
32
|
+
callbacks << callback
|
33
|
+
end
|
34
|
+
attr_reader :callback
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @return [Array<Proc>]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
# @since 0.13.0
|
42
|
+
attr_reader :callbacks
|
43
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Settings::KeyMatcher
|
6
|
+
# @return [String]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
SCOPE_SPLITTER = '.'
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
# @since 0.13.0
|
16
|
+
MATCHER_SCOPE_SPLITTER = '\.'
|
17
|
+
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
# @since 0.13.0
|
22
|
+
GENERIC_PART_PATTERN = '*'
|
23
|
+
|
24
|
+
# @return [String]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
# @since 0.13.0
|
28
|
+
GENERIC_REGEXP_PATTERN = '[^\.]+\.'
|
29
|
+
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
# @since 0.13.0
|
34
|
+
INFINITE_PART_PATTERN = '#'
|
35
|
+
|
36
|
+
# @return [String]
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
# @since 0.13.0
|
40
|
+
INFINITE_REGEXP_PATTERN = '\.*.*'
|
41
|
+
|
42
|
+
# @param scope_pattern [String]
|
43
|
+
# @return [void]
|
44
|
+
#
|
45
|
+
# @raise [Qonfig::ArgumentError]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
# @since 0.13.0
|
49
|
+
def initialize(scope_pattern)
|
50
|
+
raise Qonfig::ArgumentError unless scope_pattern.is_a?(String)
|
51
|
+
|
52
|
+
@scope_pattern = scope_pattern
|
53
|
+
@scope_pattern_size = count_scope_pattern_size(scope_pattern)
|
54
|
+
@pattern_matcher = build_pattern_matcher(scope_pattern)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param setting_key_pattern [String]
|
58
|
+
# @return [Boolean]
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
# @since 0.13.0
|
62
|
+
def match?(setting_key_pattern)
|
63
|
+
return false unless comparable_event_scopes?(setting_key_pattern)
|
64
|
+
!!pattern_matcher.match(setting_key_pattern)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# @return [Regexp]
|
70
|
+
#
|
71
|
+
# @api private
|
72
|
+
# @since 0.13.0
|
73
|
+
attr_reader :pattern_matcher
|
74
|
+
|
75
|
+
# @return [String]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
# @since 0.13.0
|
79
|
+
attr_reader :scope_pattern
|
80
|
+
|
81
|
+
# @return [Integer, Float::INFINITY]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
# @since 0.13.0
|
85
|
+
attr_reader :scope_pattern_size
|
86
|
+
|
87
|
+
# @param scope_pattern [String]
|
88
|
+
# @return [Integer, Float::INFINITY]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
# @since 0.13.0
|
92
|
+
def count_scope_pattern_size(scope_pattern)
|
93
|
+
return Float::INFINITY if scope_pattern == INFINITE_PART_PATTERN
|
94
|
+
return Float::INFINITY if scope_pattern.include?('.#')
|
95
|
+
return Float::INFINITY if scope_pattern.include?('#.')
|
96
|
+
return Float::INFINITY if scope_pattern.include?('.#.')
|
97
|
+
|
98
|
+
scope_pattern.split(SCOPE_SPLITTER).size
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param setting_key_pattern [String]
|
102
|
+
# @return [Integer]
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
# @since 0.13.0
|
106
|
+
def count_setting_key_pattern_size(setting_key_pattern)
|
107
|
+
setting_key_pattern.split(SCOPE_SPLITTER).size
|
108
|
+
end
|
109
|
+
|
110
|
+
# @param setting_key_pattern [String]
|
111
|
+
# @return [Boolean]
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
# @since 0.13.0
|
115
|
+
def comparable_event_scopes?(setting_key_pattern)
|
116
|
+
# NOTE: Integer#finite?, Integer#infinite?, Float#finite?, Float#nfinite?
|
117
|
+
# Cant be used (backward compatability with old ruby versions)
|
118
|
+
return true if scope_pattern_size == Float::INFINITY
|
119
|
+
scope_pattern_size == count_setting_key_pattern_size(setting_key_pattern)
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param pattern [String, NilClass]
|
123
|
+
# @return [Boolean]
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
# @since 0.13.0
|
127
|
+
def non_generic_pattern?(pattern = nil)
|
128
|
+
return false unless pattern
|
129
|
+
pattern != GENERIC_REGEXP_PATTERN && pattern != INFINITE_REGEXP_PATTERN
|
130
|
+
end
|
131
|
+
|
132
|
+
# "\.test\.created\.today\." => "test\.created\.today"
|
133
|
+
#
|
134
|
+
# @param regexp_string [String]
|
135
|
+
# @option left [Boolean]
|
136
|
+
# @option right [Boolean]
|
137
|
+
# @return [String]
|
138
|
+
#
|
139
|
+
# @api private
|
140
|
+
# @since 0.13.0
|
141
|
+
def strip_regexp_string(regexp_string, left: false, right: false)
|
142
|
+
pattern = regexp_string
|
143
|
+
pattern = pattern[2..-1] if left && pattern[0..1] == MATCHER_SCOPE_SPLITTER
|
144
|
+
pattern = pattern[0..-3] if right && pattern[-2..-1] == MATCHER_SCOPE_SPLITTER
|
145
|
+
pattern
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param scope_pattern [String]
|
149
|
+
# @return [Regexp]
|
150
|
+
#
|
151
|
+
# @api private
|
152
|
+
# @since 0.13.0
|
153
|
+
def build_pattern_matcher(scope_pattern)
|
154
|
+
routing_parts = scope_pattern.split(SCOPE_SPLITTER)
|
155
|
+
|
156
|
+
regexp_string = routing_parts.each_with_object([]) do |routing_part, regexp_parts|
|
157
|
+
case routing_part
|
158
|
+
when GENERIC_PART_PATTERN
|
159
|
+
regexp_parts << GENERIC_REGEXP_PATTERN
|
160
|
+
when INFINITE_PART_PATTERN
|
161
|
+
if non_generic_pattern?(regexp_parts.last)
|
162
|
+
regexp_parts[-1] = strip_regexp_string(regexp_parts.last, right: true)
|
163
|
+
end
|
164
|
+
|
165
|
+
regexp_parts << INFINITE_REGEXP_PATTERN
|
166
|
+
else
|
167
|
+
regexp_parts << (Regexp.escape(routing_part) + MATCHER_SCOPE_SPLITTER)
|
168
|
+
end
|
169
|
+
end.join
|
170
|
+
|
171
|
+
regexp_string = strip_regexp_string(regexp_string, left: true, right: true)
|
172
|
+
|
173
|
+
Regexp.new('\A' + regexp_string + '\z')
|
174
|
+
end
|
175
|
+
end
|
@@ -17,7 +17,7 @@ class Qonfig::Settings::Lock
|
|
17
17
|
# @api private
|
18
18
|
# @since 0.2.0
|
19
19
|
def thread_safe_definition(&instructions)
|
20
|
-
definition_lock.synchronize(&instructions)
|
20
|
+
definition_lock.owned? ? yield : definition_lock.synchronize(&instructions)
|
21
21
|
end
|
22
22
|
|
23
23
|
# @param instructions [Proc]
|
@@ -26,7 +26,7 @@ class Qonfig::Settings::Lock
|
|
26
26
|
# @api private
|
27
27
|
# @since 0.2.0
|
28
28
|
def thread_safe_access(&instructions)
|
29
|
-
access_lock.synchronize(&instructions)
|
29
|
+
access_lock.owned? ? yield : access_lock.synchronize(&instructions)
|
30
30
|
end
|
31
31
|
|
32
32
|
# @param instructions [Proc]
|
@@ -35,7 +35,7 @@ class Qonfig::Settings::Lock
|
|
35
35
|
# @api private
|
36
36
|
# @since 0.2.0
|
37
37
|
def thread_safe_merge(&instructions)
|
38
|
-
merge_lock.synchronize(&instructions)
|
38
|
+
merge_lock.owned? ? yield : merge_lock.synchronize(&instructions)
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator
|
6
|
+
require_relative 'validator/basic'
|
7
|
+
require_relative 'validator/method_based'
|
8
|
+
require_relative 'validator/proc_based'
|
9
|
+
require_relative 'validator/builder'
|
10
|
+
require_relative 'validator/collection'
|
11
|
+
require_relative 'validator/predefined'
|
12
|
+
require_relative 'validator/dsl'
|
13
|
+
|
14
|
+
# @param data_set [Qonfig::DataSet]
|
15
|
+
# @return [void]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
# @since 0.13.0
|
19
|
+
def initialize(data_set)
|
20
|
+
@data_set = data_set
|
21
|
+
@validators = data_set.class.validators.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [void]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
# @since 0.13.0
|
28
|
+
def validate!
|
29
|
+
validators.each do |validator|
|
30
|
+
validator.validate(data_set)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
# @since 0.13.0
|
38
|
+
def valid?
|
39
|
+
validate!
|
40
|
+
true
|
41
|
+
rescue Qonfig::ValidationError
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [Qonfig::DataSet]
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
# @since 0.13.0
|
51
|
+
attr_reader :data_set
|
52
|
+
|
53
|
+
# @return [Qonfig::Validator::Collection]
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
# @since 0.13.0
|
57
|
+
attr_reader :validators
|
58
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::Basic
|
6
|
+
# @return [String, Symbol, NilClass]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
attr_reader :setting_key_matcher
|
11
|
+
|
12
|
+
# @param setting_key_matcher [Qonfig::Settings::KeyMatcher, NilClass]
|
13
|
+
# @return [void]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
# @since 0.13.0
|
17
|
+
def initialize(setting_key_matcher)
|
18
|
+
@setting_key_matcher = setting_key_matcher
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param data_set [Qonfig::DataSet]
|
22
|
+
# @return [Boolean]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
# @since 0.13.0
|
26
|
+
def validate(data_set)
|
27
|
+
setting_key_provided? ? validate_concrete(data_set) : validate_full(data_set)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @return [Boolean]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
# @since 0.13.0
|
36
|
+
def setting_key_provided?
|
37
|
+
!setting_key_matcher.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param data_set [Qonfig::DataSet]
|
41
|
+
# @return [Any]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
# @since 0.13.0
|
45
|
+
def validate_full(data_set); end
|
46
|
+
|
47
|
+
# @param data_set [Qonfig::DataSet]
|
48
|
+
# @return [Any]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
# @since 0.13.0
|
52
|
+
def validate_concrete(data_set); end
|
53
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::Builder
|
6
|
+
require_relative 'builder/attribute_consistency'
|
7
|
+
|
8
|
+
# @return [NilClass]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.13.0
|
12
|
+
EMPTY_SETTING_KEY_PATTERN = nil
|
13
|
+
|
14
|
+
# @return [NilClass]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @since 0.13.0
|
18
|
+
NO_RUNTIME_VALIDATION_METHOD = nil
|
19
|
+
|
20
|
+
# @return [NilClass]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
# @since 0.13.0
|
24
|
+
NO_VALIDATION_LOGIC = nil
|
25
|
+
|
26
|
+
# @return [NilClass]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
# @since 0.13.0
|
30
|
+
NO_PREDEFINED_VALIDATOR = nil
|
31
|
+
|
32
|
+
class << self
|
33
|
+
# @option setting_key_pattern [String, Symbol, NilClass]
|
34
|
+
# @option predefined_validator [String, Symbol, NilClass]
|
35
|
+
# @option runtime_validation_method [String, Symbol, NilClass]
|
36
|
+
# @option validation_logic [Proc, NilClass]
|
37
|
+
# @return [Qonfig::Validator::MethodBased, Qonfig::Validator::ProcBased]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
# @since 0.13.0
|
41
|
+
def build(
|
42
|
+
setting_key_pattern: EMPTY_SETTING_KEY_PATTERN,
|
43
|
+
runtime_validation_method: NO_RUNTIME_VALIDATION_METHOD,
|
44
|
+
validation_logic: NO_VALIDATION_LOGIC,
|
45
|
+
predefined_validator: NO_PREDEFINED_VALIDATOR
|
46
|
+
)
|
47
|
+
new(
|
48
|
+
setting_key_pattern,
|
49
|
+
predefined_validator,
|
50
|
+
runtime_validation_method,
|
51
|
+
validation_logic
|
52
|
+
).build
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param setting_key_pattern [String, Symbol, NilClass]
|
57
|
+
# @param predefined_validator [String, Symbol, NilClass]
|
58
|
+
# @param runtime_validation_method [String, Symbol, NilClass]
|
59
|
+
# @param validation_logic [Proc, NilClass]
|
60
|
+
# @return [void]
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
# @since 0.13.0
|
64
|
+
def initialize(
|
65
|
+
setting_key_pattern,
|
66
|
+
predefined_validator,
|
67
|
+
runtime_validation_method,
|
68
|
+
validation_logic
|
69
|
+
)
|
70
|
+
@setting_key_pattern = setting_key_pattern
|
71
|
+
@predefined_validator = predefined_validator
|
72
|
+
@runtime_validation_method = runtime_validation_method
|
73
|
+
@validation_logic = validation_logic
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Qonfig::Validator::MethodBased, Qonfig::Validator::ProcBased]
|
77
|
+
#
|
78
|
+
# @api private
|
79
|
+
# @since 0.13.0
|
80
|
+
def build
|
81
|
+
validate_attributes!
|
82
|
+
build_validator
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# @return [String, Symbol, NilClass]
|
88
|
+
#
|
89
|
+
# @api private
|
90
|
+
# @since 0.13.0
|
91
|
+
attr_reader :setting_key_pattern
|
92
|
+
|
93
|
+
# @return [String, Symbol, NilClass]
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
# @since 0.13.0
|
97
|
+
attr_reader :predefined_validator
|
98
|
+
|
99
|
+
# @return [String, Symbol, NilClass]
|
100
|
+
#
|
101
|
+
# @api private
|
102
|
+
# @since 0.13.0
|
103
|
+
attr_reader :runtime_validation_method
|
104
|
+
|
105
|
+
# @return [Proc, NilClass]
|
106
|
+
#
|
107
|
+
# @api private
|
108
|
+
# @since 0.13.0
|
109
|
+
attr_reader :validation_logic
|
110
|
+
|
111
|
+
# @return [void]
|
112
|
+
#
|
113
|
+
# @raise [Qonfig::ArgumentError]
|
114
|
+
#
|
115
|
+
# @api private
|
116
|
+
# @since 0.13.0
|
117
|
+
def validate_attributes!
|
118
|
+
AttributeConsistency.check!(
|
119
|
+
setting_key_pattern,
|
120
|
+
predefined_validator,
|
121
|
+
runtime_validation_method,
|
122
|
+
validation_logic
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [Qonfig::Validator::MethodBased, Qonfig::Validator::PorcBased]
|
127
|
+
#
|
128
|
+
# @api private
|
129
|
+
# @since 0.13.0
|
130
|
+
def build_validator
|
131
|
+
case
|
132
|
+
when predefined_validator then build_predefined
|
133
|
+
when runtime_validation_method then build_method_based
|
134
|
+
when validation_logic then build_proc_based
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [Qonfig::Settings::KeyMatcher, NilClass]
|
139
|
+
#
|
140
|
+
# @api private
|
141
|
+
# @since 0.13.0
|
142
|
+
def build_setting_key_matcher
|
143
|
+
Qonfig::Settings::KeyMatcher.new(setting_key_pattern.to_s) if setting_key_pattern
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [Qonfig::Validator::MethodBased]
|
147
|
+
#
|
148
|
+
# @api private
|
149
|
+
# @since 0.13.0
|
150
|
+
def build_method_based
|
151
|
+
Qonfig::Validator::MethodBased.new(build_setting_key_matcher, runtime_validation_method)
|
152
|
+
end
|
153
|
+
|
154
|
+
# @return [Qonfig::Validator::ProcBased]
|
155
|
+
#
|
156
|
+
# @api private
|
157
|
+
# @since 0.13.0
|
158
|
+
def build_proc_based
|
159
|
+
Qonfig::Validator::ProcBased.new(build_setting_key_matcher, validation_logic)
|
160
|
+
end
|
161
|
+
|
162
|
+
# @return [Qonfig::Settings::Predefined]
|
163
|
+
#
|
164
|
+
# @api private
|
165
|
+
# @since 0.13.0
|
166
|
+
def build_predefined
|
167
|
+
Qonfig::Validator::Predefined.build(predefined_validator, build_setting_key_matcher)
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::Builder::AttributeConsistency
|
6
|
+
class << self
|
7
|
+
# @param setting_key_pattern [String, Symbol, NilClass]
|
8
|
+
# @param predefined_validator [String, Symbol, NilClass]
|
9
|
+
# @param runtime_validation_method [String, Symbol, NilClass]
|
10
|
+
# @param validation_logic [Proc, NilClass]
|
11
|
+
# @return [void]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
# @since 0.13.0
|
15
|
+
def check!(
|
16
|
+
setting_key_pattern,
|
17
|
+
predefined_validator,
|
18
|
+
runtime_validation_method,
|
19
|
+
validation_logic
|
20
|
+
)
|
21
|
+
new(
|
22
|
+
setting_key_pattern,
|
23
|
+
predefined_validator,
|
24
|
+
runtime_validation_method,
|
25
|
+
validation_logic
|
26
|
+
).check!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param setting_key_pattern [String, Symbol, NilClass]
|
31
|
+
# @param predefined_validator [String, Symbol, NilClass]
|
32
|
+
# @param runtime_validation_method [String, Symbol, NilClass]
|
33
|
+
# @param validation_logic [Proc, NilClass]
|
34
|
+
# @return [void]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
# @since 0.13.0
|
38
|
+
def initialize(
|
39
|
+
setting_key_pattern,
|
40
|
+
predefined_validator,
|
41
|
+
runtime_validation_method,
|
42
|
+
validation_logic
|
43
|
+
)
|
44
|
+
@setting_key_pattern = setting_key_pattern
|
45
|
+
@predefined_validator = predefined_validator
|
46
|
+
@runtime_validation_method = runtime_validation_method
|
47
|
+
@validation_logic = validation_logic
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [void]
|
51
|
+
#
|
52
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
# @since 0.13.0
|
56
|
+
def check!
|
57
|
+
consistent_method_choice!
|
58
|
+
consistent_predefined_validator!
|
59
|
+
cosnistent_runtime_validation_method!
|
60
|
+
consistent_validation_logic!
|
61
|
+
consistent_setting_key_pattern!
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @return [String, Symbol, NilClass]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
# @since 0.13.0
|
70
|
+
attr_reader :setting_key_pattern
|
71
|
+
|
72
|
+
# @return [String, Symbol, NilClass]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
# @since 0.13.0
|
76
|
+
attr_reader :predefined_validator
|
77
|
+
|
78
|
+
# @return [String, Symbol, NilClass]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
# @since 0.13.0
|
82
|
+
attr_reader :runtime_validation_method
|
83
|
+
|
84
|
+
# @return [Proc, NilClass]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
# @since 0.13.0
|
88
|
+
attr_reader :validation_logic
|
89
|
+
|
90
|
+
# @return [void]
|
91
|
+
#
|
92
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
# @since 0.13.0
|
96
|
+
def consistent_method_choice!
|
97
|
+
unless runtime_validation_method || validation_logic || predefined_validator
|
98
|
+
raise(
|
99
|
+
Qonfig::ValidatorArgumentError,
|
100
|
+
'Empty validation (you should provide: dataset method OR proc OR predefined validator)'
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
if ((runtime_validation_method && validation_logic) ||
|
105
|
+
(predefined_validator && (runtime_validation_method || validation_logic)))
|
106
|
+
raise(
|
107
|
+
Qonfig::ValidatorArgumentError,
|
108
|
+
'Incosistent validation (you should use: dataset method OR proc OR predefined validator)'
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [void]
|
114
|
+
#
|
115
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
116
|
+
#
|
117
|
+
# @api private
|
118
|
+
# @since 0.13.0
|
119
|
+
def consistent_predefined_validator!
|
120
|
+
return if predefined_validator.nil?
|
121
|
+
return if predefined_validator.is_a?(Symbol)
|
122
|
+
return if predefined_validator.is_a?(String)
|
123
|
+
|
124
|
+
raise(
|
125
|
+
Qonfig::ValidatorArgumentError,
|
126
|
+
'Incorrect name of predefined validator (should be a symbol or a string)'
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @return [void]
|
131
|
+
#
|
132
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
# @since 0.13.0
|
136
|
+
def cosnistent_runtime_validation_method!
|
137
|
+
return if runtime_validation_method.nil?
|
138
|
+
return if runtime_validation_method.is_a?(Symbol)
|
139
|
+
return if runtime_validation_method.is_a?(String)
|
140
|
+
|
141
|
+
raise(
|
142
|
+
Qonfig::ValidatorArgumentError,
|
143
|
+
'Incompatible validation method name (should be a symbol or a string)'
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [void]
|
148
|
+
#
|
149
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
150
|
+
#
|
151
|
+
# @api private
|
152
|
+
# @since 0.13.0
|
153
|
+
def consistent_validation_logic!
|
154
|
+
return if validation_logic.nil?
|
155
|
+
return if validation_logic.is_a?(Proc)
|
156
|
+
|
157
|
+
# :nocov:
|
158
|
+
raise(
|
159
|
+
Qonfig::ValidatorArgumentError,
|
160
|
+
'Incompatible validation object (should be a proc)'
|
161
|
+
)
|
162
|
+
# :nocov:
|
163
|
+
end
|
164
|
+
|
165
|
+
# @return [void]
|
166
|
+
#
|
167
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
168
|
+
#
|
169
|
+
# @api private
|
170
|
+
# @since 0.13.0
|
171
|
+
def consistent_setting_key_pattern!
|
172
|
+
return if setting_key_pattern.nil?
|
173
|
+
return if setting_key_pattern.is_a?(Symbol)
|
174
|
+
return if setting_key_pattern.is_a?(String)
|
175
|
+
|
176
|
+
raise(
|
177
|
+
Qonfig::ValidatorArgumentError,
|
178
|
+
'Incompatible setting key pattern (should be a string or a symbol)'
|
179
|
+
)
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::Collection
|
6
|
+
# @api private
|
7
|
+
# @since 0.13.0
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# @return [Array<Qonfig::Validator::MethodBased,Qonfig::Validator::ProcBased>]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 0.13.0
|
14
|
+
attr_reader :validators
|
15
|
+
|
16
|
+
# @return [void]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @since 0.13.0
|
20
|
+
def initialize
|
21
|
+
@validators = []
|
22
|
+
@access_lock = Mutex.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param validator [Qonfig::Validator::MethodBased, Qonfig::Validator::ProcBased]
|
26
|
+
# @return [void]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
# @since 0.13.0
|
30
|
+
def add_validator(validator)
|
31
|
+
thread_safe { validators << validator }
|
32
|
+
end
|
33
|
+
alias_method :<<, :add_validator
|
34
|
+
|
35
|
+
# @param block [Proc]
|
36
|
+
# @return [Enumerable]
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
# @since 0.13.0
|
40
|
+
def each(&block)
|
41
|
+
thread_safe { block_given? ? validators.each(&block) : validators.each }
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param collection [Qonfig::Validator::Collection]
|
45
|
+
# @return [void]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
# @since 0.13.0
|
49
|
+
def concat(collection)
|
50
|
+
thread_safe { validators.concat(collection.validators) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Qonfig::Validator::Collection]
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
# @since 0.13.0
|
57
|
+
def dup
|
58
|
+
thread_safe do
|
59
|
+
self.class.new.tap { |duplicate| duplicate.concat(self) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# @param block [Proc]
|
66
|
+
# @return [Any]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
# @since 0.13.0
|
70
|
+
def thread_safe(&block)
|
71
|
+
@access_lock.synchronize(&block)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
module Qonfig::Validator::DSL
|
6
|
+
class << self
|
7
|
+
# @param child_klass [Qonfig::DataSet]
|
8
|
+
# @return [void]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.13.0
|
12
|
+
def extended(child_klass)
|
13
|
+
child_klass.instance_variable_set(:@validators, Qonfig::Validator::Collection.new)
|
14
|
+
|
15
|
+
child_klass.singleton_class.prepend(Module.new do
|
16
|
+
def inherited(child_klass)
|
17
|
+
child_klass.instance_variable_set(:@validators, Qonfig::Validator::Collection.new)
|
18
|
+
child_klass.validators.concat(validators)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Qonfig::Validator::Collection]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
# @since 0.13.0
|
29
|
+
def validators
|
30
|
+
@validators
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param setting_key_pattern [String, Symbol, NilClass]
|
34
|
+
# @param predefined [String, Symbol]
|
35
|
+
# @option by [String, Symbol, NilClass]
|
36
|
+
# @param custom_validation [Proc]
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @see Qonfig::Validator::Builder
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.13.0
|
43
|
+
def validate(setting_key_pattern = nil, predefined = nil, by: nil, &custom_validation)
|
44
|
+
validators << Qonfig::Validator::Builder.build(
|
45
|
+
setting_key_pattern: setting_key_pattern,
|
46
|
+
predefined_validator: predefined,
|
47
|
+
runtime_validation_method: by,
|
48
|
+
validation_logic: custom_validation
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::MethodBased < Qonfig::Validator::Basic
|
6
|
+
# @return [Symbol, String]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
attr_reader :runtime_validation_method
|
11
|
+
|
12
|
+
# @param setting_key_matcher [Qonfig::Settings::KeyMatcher, NilClass]
|
13
|
+
# @param runtime_validation_method [String, Symbol]
|
14
|
+
# @return [void]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @since 0.13.0
|
18
|
+
def initialize(setting_key_matcher, runtime_validation_method)
|
19
|
+
super(setting_key_matcher)
|
20
|
+
@runtime_validation_method = runtime_validation_method
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param data_set [Qonfig::DataSet]
|
24
|
+
# @return [Boolean]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
# @since 0.13.0
|
28
|
+
def validate_concrete(data_set)
|
29
|
+
data_set.settings.__deep_each_setting__ do |setting_key, setting_value|
|
30
|
+
next unless setting_key_matcher.match?(setting_key)
|
31
|
+
|
32
|
+
raise(
|
33
|
+
Qonfig::ValidationError,
|
34
|
+
"Invalid value of setting <#{setting_key}> (#{setting_value})"
|
35
|
+
) unless data_set.__send__(runtime_validation_method, setting_value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param data_set [Qonfig::DataSet]
|
40
|
+
# @return [Boolean]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
# @since 0.13.0
|
44
|
+
def validate_full(data_set)
|
45
|
+
unless data_set.__send__(runtime_validation_method)
|
46
|
+
raise(Qonfig::ValidationError, 'Invalid config object')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
module Qonfig::Validator::Predefined
|
6
|
+
require_relative 'predefined/common'
|
7
|
+
require_relative 'predefined/registry'
|
8
|
+
require_relative 'predefined/registry_control_mixin'
|
9
|
+
|
10
|
+
# @since 0.13.0
|
11
|
+
extend Qonfig::Validator::Predefined::RegistryControlMixin
|
12
|
+
|
13
|
+
# @since 0.13.0
|
14
|
+
predefine(:integer) { |value| value.is_a?(Integer) }
|
15
|
+
# @since 0.13.0
|
16
|
+
predefine(:float) { |value| value.is_a?(Float) }
|
17
|
+
# @since 0.13.0
|
18
|
+
predefine(:numeric) { |value| value.is_a?(Numeric) }
|
19
|
+
# @since 0.13.0
|
20
|
+
predefine(:string) { |value| value.is_a?(String) }
|
21
|
+
# @since 0.13.0
|
22
|
+
predefine(:symbol) { |value| value.is_a?(Symbol) }
|
23
|
+
# @since 0.13.0
|
24
|
+
predefine(:text) { |value| value.is_a?(Symbol) || value.is_a?(String) }
|
25
|
+
# @since 0.13.0
|
26
|
+
predefine(:array) { |value| value.is_a?(Array) }
|
27
|
+
# @since 0.13.0
|
28
|
+
predefine(:hash) { |value| value.is_a?(Hash) }
|
29
|
+
# @since 0.13.0
|
30
|
+
predefine(:big_decimal) { |value| value.is_a?(BigDecimal) }
|
31
|
+
# @since 0.13.0
|
32
|
+
predefine(:boolean) { |value| value.is_a?(TrueClass) || value.is_a?(FalseClass) }
|
33
|
+
# @since 0.13.0
|
34
|
+
predefine(:proc) { |value| value.is_a?(Proc) }
|
35
|
+
# @since 0.13.0
|
36
|
+
predefine(:class) { |value| value.is_a?(Class) }
|
37
|
+
# @since 0.13.0
|
38
|
+
predefine(:module) { |value| value.is_a?(Module) }
|
39
|
+
# @since 0.13.0
|
40
|
+
predefine(:not_nil) { |value| !value.nil? }
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::Predefined::Common < Qonfig::Validator::Basic
|
6
|
+
# @return [Proc]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
attr_reader :validation
|
11
|
+
|
12
|
+
# @param setting_key_matcher [Qonfig::Settings::KeyMatcher]
|
13
|
+
# @param validation [Proc]
|
14
|
+
# @return [void]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @since 0.13.0
|
18
|
+
def initialize(setting_key_matcher, validation)
|
19
|
+
super(setting_key_matcher)
|
20
|
+
@validation = validation
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param data_set [Qonfig::DataSet]
|
24
|
+
# @return [void]
|
25
|
+
#
|
26
|
+
# @raise [Qonfig::ValidationError]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
# @since 0.13.0
|
30
|
+
def validate_concrete(data_set)
|
31
|
+
data_set.settings.__deep_each_setting__ do |setting_key, setting_value|
|
32
|
+
next unless setting_key_matcher.match?(setting_key)
|
33
|
+
|
34
|
+
raise(
|
35
|
+
Qonfig::ValidationError,
|
36
|
+
"Invalid value of setting <#{setting_key}> (#{setting_value})"
|
37
|
+
) unless validation.call(setting_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param data_set [Qonfig::DataSet]
|
42
|
+
# @return [void]
|
43
|
+
#
|
44
|
+
# @raise [Qonfig::ValidationError]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
# @since 0.13.0
|
48
|
+
def validate_full(data_set)
|
49
|
+
# :nocov:
|
50
|
+
raise Qonfig::Error, 'Predefined validator should be used only with a setting key'
|
51
|
+
# :nocov:
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::Predefined::Registry
|
6
|
+
# @return [void]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
def initialize
|
11
|
+
@validators = {}
|
12
|
+
@lock = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param name [String, Symbol]
|
16
|
+
# @param validation [Proc]
|
17
|
+
# @return [void]
|
18
|
+
#
|
19
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
# @since 0.13.0
|
23
|
+
def register(name, &validation)
|
24
|
+
thread_safe do
|
25
|
+
name = indifferently_accessable_name(name)
|
26
|
+
|
27
|
+
raise(
|
28
|
+
Qonfig::ValidatorArgumentError,
|
29
|
+
"Predefined validator with name '#{name}' already exists."
|
30
|
+
) if validators.key?(name)
|
31
|
+
|
32
|
+
validators[name] = validation
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param name [String, Symbol]
|
37
|
+
# @return [Qonfig::Validator::ProcBased, Qonfig::Validator::MethodBased]
|
38
|
+
#
|
39
|
+
# @raise [Qonfig::ValidatorArgumentError]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.13.0
|
43
|
+
def resolve(name)
|
44
|
+
thread_safe do
|
45
|
+
# rubocop:disable Style/RedundantBegin
|
46
|
+
begin
|
47
|
+
validators.fetch(indifferently_accessable_name(name))
|
48
|
+
rescue KeyError
|
49
|
+
raise(
|
50
|
+
Qonfig::ValidatorArgumentError,
|
51
|
+
"Predefined validator with name '#{name}' does not exist."
|
52
|
+
)
|
53
|
+
end
|
54
|
+
# rubocop:enable Style/RedundantBegin
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# @return [Hash<String,Proc>]
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
# @since 0.13.0
|
64
|
+
attr_reader :validators
|
65
|
+
|
66
|
+
# @param name [String, Symbol]
|
67
|
+
# @return [String]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
# @since 0.13.0
|
71
|
+
def indifferently_accessable_name(name)
|
72
|
+
name.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param block [Proc]
|
76
|
+
# @return [Any]
|
77
|
+
#
|
78
|
+
# @api private
|
79
|
+
# @since 0.13.0
|
80
|
+
def thread_safe(&block)
|
81
|
+
@lock.synchronize(&block)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
module Qonfig::Validator::Predefined::RegistryControlMixin
|
6
|
+
class << self
|
7
|
+
# @param basic_klass [Class, Module]
|
8
|
+
# @return [void]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.13.0
|
12
|
+
def extended(basic_klass)
|
13
|
+
basic_klass.instance_variable_set(:@registry, Qonfig::Validator::Predefined::Registry.new)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Qonfig::Validator::Predefined::Registry]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
# @since 0.13.0
|
21
|
+
attr_reader :registry
|
22
|
+
|
23
|
+
# @param name [String, Symbol]
|
24
|
+
# @param validation [Proc]
|
25
|
+
# @return [void]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
# @since 0.13.0
|
29
|
+
def predefine(name, &validation)
|
30
|
+
registry.register(name, &validation)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param name [String, Symbol]
|
34
|
+
# @param setting_key_matcher [Qonfig::Setting::KeyMatcher]
|
35
|
+
# @return [Qonfig::Validator::Predefined::Common]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
# @since 0.13.0
|
39
|
+
def build(name, setting_key_matcher)
|
40
|
+
validation = registry.resolve(name)
|
41
|
+
Qonfig::Validator::Predefined::Common.new(setting_key_matcher, validation)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.13.0
|
5
|
+
class Qonfig::Validator::ProcBased < Qonfig::Validator::Basic
|
6
|
+
# @return [Proc]
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.13.0
|
10
|
+
attr_reader :validation
|
11
|
+
|
12
|
+
# @param setting_key_matcher [Qonfig::Settings::KeyMatcher, NilClass]
|
13
|
+
# @param vaidation [Proc]
|
14
|
+
# @return [void]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @since 0.13.0
|
18
|
+
def initialize(setting_key_matcher, validation)
|
19
|
+
super(setting_key_matcher)
|
20
|
+
@validation = validation
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param data_set [Qonfig::DataSet]
|
24
|
+
# @return [Boolean]
|
25
|
+
#
|
26
|
+
# @raise [Qonfig::ValidationError]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
# @since 0.13.0
|
30
|
+
def validate_concrete(data_set)
|
31
|
+
data_set.settings.__deep_each_setting__ do |setting_key, setting_value|
|
32
|
+
next unless setting_key_matcher.match?(setting_key)
|
33
|
+
|
34
|
+
raise(
|
35
|
+
Qonfig::ValidationError,
|
36
|
+
"Invalid value of setting <#{setting_key}> (#{setting_value})"
|
37
|
+
) unless data_set.instance_exec(setting_value, &validation)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param data_set [Qonfig::DataSet]
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @raise [Qonfig::ValidationError]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
# @since 0.13.0
|
48
|
+
def validate_full(data_set)
|
49
|
+
unless data_set.instance_eval(&validation)
|
50
|
+
raise(Qonfig::ValidationError, 'Invalid config object')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -5,5 +5,5 @@ module Qonfig
|
|
5
5
|
#
|
6
6
|
# @api public
|
7
7
|
# @since 0.1.0
|
8
|
-
VERSION = '0.
|
8
|
+
VERSION = '0.13.0'
|
9
9
|
end
|
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_development_dependency 'coveralls', '~> 0.8'
|
32
32
|
spec.add_development_dependency 'simplecov', '~> 0.16'
|
33
33
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
34
|
-
spec.add_development_dependency 'armitage-rubocop', '~> 0.
|
34
|
+
spec.add_development_dependency 'armitage-rubocop', '~> 0.74'
|
35
35
|
|
36
36
|
spec.add_development_dependency 'bundler'
|
37
37
|
spec.add_development_dependency 'rake'
|
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qonfig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rustam Ibragimov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: coveralls
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
61
|
+
version: '0.74'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
68
|
+
version: '0.74'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: bundler
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,7 +149,7 @@ files:
|
|
149
149
|
- lib/qonfig/configurable.rb
|
150
150
|
- lib/qonfig/data_set.rb
|
151
151
|
- lib/qonfig/data_set/class_builder.rb
|
152
|
-
- lib/qonfig/data_set/
|
152
|
+
- lib/qonfig/data_set/lock.rb
|
153
153
|
- lib/qonfig/dsl.rb
|
154
154
|
- lib/qonfig/errors.rb
|
155
155
|
- lib/qonfig/loaders.rb
|
@@ -170,13 +170,27 @@ files:
|
|
170
170
|
- lib/qonfig/plugins/toml/uploaders/toml.rb
|
171
171
|
- lib/qonfig/settings.rb
|
172
172
|
- lib/qonfig/settings/builder.rb
|
173
|
+
- lib/qonfig/settings/callbacks.rb
|
173
174
|
- lib/qonfig/settings/key_guard.rb
|
175
|
+
- lib/qonfig/settings/key_matcher.rb
|
174
176
|
- lib/qonfig/settings/lock.rb
|
175
177
|
- lib/qonfig/uploaders.rb
|
176
178
|
- lib/qonfig/uploaders/base.rb
|
177
179
|
- lib/qonfig/uploaders/file.rb
|
178
180
|
- lib/qonfig/uploaders/json.rb
|
179
181
|
- lib/qonfig/uploaders/yaml.rb
|
182
|
+
- lib/qonfig/validator.rb
|
183
|
|