qonfig 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7f383ceef309126a60c17c1383cdfd3de9445aa5978ccb80374226dfd17e83c
4
- data.tar.gz: 6f2f868550a9822b4feb8daff758a2a91d486903f9e57726c1045725fc658e55
3
+ metadata.gz: 881009df231adc7035ec25267ffe8d63a58a66878eb5017cc8a8f2084bd5753d
4
+ data.tar.gz: de6d094271c5a42737b1f868cb5e6ec61d4e1c15090bf69249f4147ae168ae15
5
5
  SHA512:
6
- metadata.gz: bf030ed32f26241ebe6a8717e02e3b705d5f31e666c1096f19776c14e2db92f251e08ee12e668831a69bea51ce2fb40288791dbc4d8dfd19bc9fdd10c0505db8
7
- data.tar.gz: 67e3f1712357a8de30e651271b8ca2dd2590af18cedd42ee57d48b86a755bfe15a0ea7ae59fea448212ae5fa798b19a12364acaa2fde588fc5778c33ef4d25c1
6
+ metadata.gz: d65fa945c02683ed445089bfec0f558eda6db5e34157ed315b781cf4410302b5c9a7908580f484ea82657a211674d1a04615291ec81e3fda127d45accf060858
7
+ data.tar.gz: 6dab7357d7315839c5aeeb8a3a67b53cda1ad535847635ba099f37a979d43517c1b464d85eb7b37bd85c5864c5ee6ac8eb3eadce5ea3bb7f23367a45b34fe30c
data/.gitignore CHANGED
@@ -13,3 +13,4 @@ Gemfile.lock
13
13
  /.vscode/
14
14
  /spec/artifacts/
15
15
  /gemfiles/*.gemfile.lock
16
+ *.gem
data/.rubocop.yml CHANGED
@@ -13,3 +13,7 @@ AllCops:
13
13
  - qonfig.gemspec
14
14
  - bin/console
15
15
  - bin/rspec
16
+
17
+ # NOTE: for code clarity in tests
18
+ RSpec/LeakyConstantDeclaration:
19
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,6 +1,21 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [0.14.0] - 2019-08-28
5
+ ### Added
6
+ - `expose_json`
7
+ - a command that provides an ability to define config settings by loading them from a json file
8
+ where the concrete settings depends on the chosen environment;
9
+ - works in `expose_yaml` manner;
10
+ - `expose_self`
11
+ - a command that provides an ability to define config settings by loading them from the current file
12
+ where `__END__` instruction is defined (concrete settings dependes on the chosen environment);
13
+ - works with `YAML` format;
14
+
15
+ ### Changed
16
+ - `Qonfig::Settings::Callbacks` is thread safe now;
17
+ - Minor refactorings;
18
+
4
19
  ## [0.13.0] - 2019-08-13
5
20
  ### Added
6
21
  - Iteration over setting keys (`#each_setting { |key, value| }`, `#deep_each_setting { |key, value| }`);
@@ -14,6 +29,7 @@ All notable changes to this project will be documented in this file.
14
29
  - Support for **TOML** (`.toml`) format
15
30
  - realized as a plugin (`Qonfig.plugin(:toml)`);
16
31
  - provides `#save_to_toml`, `#load_from_toml`, `#expose_toml` methods and works in `#*_yaml`-like manner);
32
+ - depends on `gem toml-rb (>= 1)`
17
33
  - Custom `bin/rspec` command:
18
34
  - `bin/rspec -n` - run tests without plugin tests;
19
35
  - `bin/rspec -w` - run all tests;
data/README.md CHANGED
@@ -36,6 +36,7 @@ require 'qonfig'
36
36
  - [State freeze](#state-freeze)
37
37
  - [Settings as Predicates](#settings-as-predicates)
38
38
  - [Validation](#validation)
39
+ - [Introduction](#introdaction)
39
40
  - [Key search pattern](#key-search-pattern)
40
41
  - [Proc-based validation](#proc-based-validation)
41
42
  - [Method-based validation](#method-based-validation)
@@ -44,16 +45,28 @@ require 'qonfig'
44
45
  - [Load from YAML file](#load-from-yaml-file)
45
46
  - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs)
46
47
  - [Load from JSON file](#load-from-json-file)
48
+ - [Expose JSON](#expose-json) (`Rails`-like environment-based JSON configs)
47
49
  - [Load from ENV](#load-from-env)
48
50
  - [Load from \_\_END\_\_](#load-from-__end__) (aka `load_from_self`)
51
+ - [Expose \_\_END\_\_](#expose-__end__) (aka `expose_self`)
49
52
  - [Save to JSON file](#save-to-json-file) (`save_to_json`)
50
53
  - [Save to YAML file](#save-to-yaml-file) (`save_to_yaml`)
51
54
  - [Plugins](#plugins)
52
55
  - [toml](#plugins-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`)
56
+ - [Roadmap](#roadmap)
53
57
  ---
54
58
 
55
59
  ## Definition
56
60
 
61
+ - [Definition and Settings Access](#definition-and-access)
62
+ - [Configuration](#configuration)
63
+ - [Inheritance](#inheritance)
64
+ - [Composition](#composition)
65
+ - [Hash representation](#hash-representation)
66
+ - [Smart Mixin](#smart-mixin) (`Qonfig::Configurable`)
67
+
68
+ ---
69
+
57
70
  ### Definition and Access
58
71
 
59
72
  ```ruby
@@ -396,9 +409,14 @@ GeneralApplication.config.to_h
396
409
 
397
410
  ---
398
411
 
399
-
400
412
  ## Interaction
401
413
 
414
+ - [Iteration over setting keys](#iteration-over-setting-keys) (`#each_setting`, `#deep_each_setting`)
415
+ - [Config reloading](#config-reloading) (reload config definitions and option values)
416
+ - [Clear options](#clear-options) (set to nil)
417
+ - [State freeze](#state-freeze)
418
+ - [Settings as Predicates](#settings-as-predicates)
419
+
402
420
  ---
403
421
 
404
422
  ### Iteration over setting keys
@@ -407,7 +425,8 @@ GeneralApplication.config.to_h
407
425
  - iterates over the root setting keys;
408
426
  - `#deep_each_setting { |key, value| }`
409
427
  - iterates over all setting keys (deep inside);
410
- - key object is represented as a string of `.`-joined keys;
428
+ - key object is represented as a string of `.`-joined setting key names;
429
+
411
430
 
412
431
  ```ruby
413
432
  class Config < Qonfig::DataSet
@@ -424,16 +443,24 @@ class Config < Qonfig::DataSet
424
443
  end
425
444
 
426
445
  config = Config.new
446
+ ```
447
+
448
+ #### .each_setting
427
449
 
428
- # 1. #each_setting
450
+ ```ruby
429
451
  config.each_setting { |key, value| { key => value } }
452
+
430
453
  # result of each step:
431
454
  { 'db' => <Qonfig::Settings:0x00007ff8> }
432
455
  { 'telegraf_url' => 'udp://localhost:8094' }
433
456
  { 'telegraf_prefix' => 'test' }
457
+ ```
458
+
459
+ #### .deep_each_setting
434
460
 
435
- # 2. #deep_each_setting
461
+ ```ruby
436
462
  config.deep_each_setting { |key, value| { key => value } }
463
+
437
464
  # result of each step:
438
465
  { 'db.creds.user' => 'D@iveR' }
439
466
  { 'db.creds.password' => 'test123' }
@@ -595,25 +622,32 @@ config.settings.database.engine.driver? # => true (true => true)
595
622
 
596
623
  ## Validation
597
624
 
625
+ - [Introduction](#introduction)
626
+ - [Key Search Pattern](#key-search-pattern)
627
+ - [Proc-based validation](#proc-based-validation)
628
+ - [Method-based validation](#method-based-validation)
629
+ - [Predefined validations](#predefined-validations)
630
+
631
+ ---
632
+
633
+ ### Introduction
634
+
598
635
  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)).
636
+ Settings are validated as keys (matched with a [specific string pattern](#key-search-pattern)).
600
637
  You can validate both a set of keys and each key separately.
601
638
  If you want to check the config object completely you can define a custom validation.
602
639
 
603
640
  **Features**:
604
-
605
- - is invoked on any mutation of any setting key
641
+ - validation is invoked on any mutation of any setting:
606
642
  - during dataset instantiation;
607
643
  - when assigning new values;
608
644
  - when calling `#reload!`;
609
645
  - when calling `#clear!`;
610
-
611
646
  - provides special [key search pattern](#key-search-pattern) for matching setting key names;
612
647
  - uses the [key search pattern](#key-search-pattern) for definging what the setting key should be validated;
613
648
  - you can define your own custom validation logic and validate dataset instance completely;
614
649
  - validation logic should return **truthy** or **falsy** value;
615
-
616
- - supprots two validation techniques (**proc-based** and **dataset-method-based**)
650
+ - supprots two validation techniques (**proc-based** ([doc](#proc-based-validation)) and **dataset-method-based** ([doc](#method-based-validation))):
617
651
  - **proc-based** (`setting validation`)
618
652
  ```ruby
619
653
  validate 'db.user' do |value|
@@ -642,22 +676,23 @@ If you want to check the config object completely you can define a custom valida
642
676
  settings.user == User[1]
643
677
  end
644
678
  ```
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`
679
+ - provides a **set of standard validations** ([doc](#predefined-validations)):
680
+ - DSL: `validate 'key.pattern', :predefned_validator`;
681
+ - validators:
682
+ - `integer`
683
+ - `float`
684
+ - `numeric`
685
+ - `big_decimal`
686
+ - `boolean`
687
+ - `string`
688
+ - `symbol`
689
+ - `text` (string or symbol)
690
+ - `array`
691
+ - `hash`
692
+ - `proc`
693
+ - `class`
694
+ - `module`
695
+ - `not_nil`
661
696
 
662
697
  ---
663
698
 
@@ -818,12 +853,12 @@ config.settings.enabled = nil # => Qonfig::ValidationError (should be a boolean)
818
853
 
819
854
  ```ruby
820
855
  class Config < Qonfig::DataSet
821
- setting :user
822
- setting :password
856
+ setting :user, 'empty'
857
+ setting :password, 'empty'
823
858
 
824
859
  setting :service do
825
- setting :provider
826
- setting :protocol
860
+ setting :provider, :empty
861
+ setting :protocol, :empty
827
862
  setting :on_fail, -> { puts 'atata!' }
828
863
  end
829
864
 
@@ -851,6 +886,18 @@ config.settings.ignorance = nil # => Qonfig::ValidationError (cant be nil)
851
886
 
852
887
  ## Work with files
853
888
 
889
+ - [Load from YAML file](#load-from-yaml-file)
890
+ - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs)
891
+ - [Load from JSON file](#load-from-json-file)
892
+ - [Expose JSON](#expose-json) (`Rails`-like environment-based JSON configs)
893
+ - [Load from ENV](#load-from-env)
894
+ - [Load from \_\_END\_\_](#load-from-__end__) (aka `load_from_self`)
895
+ - [Expose \_\_END\_\_](#expose-__end__) (aka `expose_self`)
896
+ - [Save to JSON file](#save-to-json-file) (`save_to_json`)
897
+ - [Save to YAML file](#save-to-yaml-file) (`save_to_yaml`)
898
+
899
+ ---
900
+
854
901
  ### Load from YAML file
855
902
 
856
903
  - supports `ERB`;
@@ -1097,6 +1144,120 @@ Config.new.to_h # => { "nonexistent_json" => {}, "another_key" => nil }
1097
1144
 
1098
1145
  ---
1099
1146
 
1147
+ ### Expose JSON
1148
+
1149
+ - load configurations from JSON file in Rails-like manner (with environments);
1150
+ - works in `load_from_jsom`/`expose_yaml` manner;
1151
+ - `via:` - how an environment will be determined:
1152
+ - `:file_name`
1153
+ - load configuration from JSON file that have an `:env` part in it's name;
1154
+ - `:env_key`
1155
+ - load configuration from JSON file;
1156
+ - concrete configuration should be defined in the root key with `:env` name;
1157
+ - `env:` - your environment name (must be a type of `String`, `Symbol` or `Numeric`);
1158
+ - `strict:` - requires the existence of the file and/or key with the name of the used environment:
1159
+ - `true`:
1160
+ - file should exist;
1161
+ - root key with `:env` name should exist (if `via: :env_key` is used);
1162
+ - raises `Qonfig::ExposeError` if file does not contain the required env key (if `via: :env` key is used);
1163
+ - raises `Qonfig::FileNotFoundError` if the required file does not exist;
1164
+ - `false`:
1165
+ - file is not required;
1166
+ - root key with `:env` name is not required (if `via: :env_key` is used);
1167
+
1168
+ #### Environment is defined as a root key of JSON file
1169
+
1170
+ ```json
1171
+ // config/project.json
1172
+
1173
+ {
1174
+ "development": {
1175
+ "api_mode_enabled": true,
1176
+ "logging": false,
1177
+ "db_driver": "sequel",
1178
+ "throttle_requests": false,
1179
+ "credentials": {}
1180
+ },
1181
+ "test": {
1182
+ "api_mode_enabled": true,
1183
+ "logging": false,
1184
+ "db_driver": "in_memory",
1185
+ "throttle_requests": false,
1186
+ "credentials": {}
1187
+ },
1188
+ "staging": {
1189
+ "api_mode_enabled": true,
1190
+ "logging": true,
1191
+ "db_driver": "active_record",
1192
+ "throttle_requests": true,
1193
+ "credentials": {}
1194
+ },
1195
+ "production": {
1196
+ "api_mode_enabled": true,
1197
+ "logging": true,
1198
+ "db_driver": "rom",
1199
+ "throttle_requests": true,
1200
+ "credentials": {}
1201
+ }
1202
+ }
1203
+ ```
1204
+
1205
+ ```ruby
1206
+ class Config < Qonfig::DataSet
1207
+ expose_json 'config/project.json', via: :env_key, env: :production # load from production env
1208
+
1209
+ # NOTE: in rails-like application you can use this:
1210
+ expose_json 'config/project.json', via: :env_key, env: Rails.env
1211
+ end
1212
+
1213
+ config = Config.new
1214
+
1215
+ config.settings.api_mode_enabled # => true (from :production subset of keys)
1216
+ config.settings.logging # => true (from :production subset of keys)
1217
+ config.settings.db_driver # => "rom" (from :production subset of keys)
1218
+ config.settings.throttle_requests # => true (from :production subset of keys)
1219
+ config.settings.credentials # => {} (from :production subset of keys)
1220
+ ```
1221
+
1222
+ #### Environment is defined as a part of JSON file name
1223
+
1224
+ ```json
1225
+ // config/sidekiq.staging.json
1226
+ {
1227
+ "web": {
1228
+ "username": "staging_admin",
1229
+ "password": "staging_password"
1230
+ }
1231
+ }
1232
+ ```
1233
+
1234
+ ```json
1235
+ // config/sidekiq.production.json
1236
+ {
1237
+ "web": {
1238
+ "username": "urj1o2",
1239
+ "password": "u192jd0ixz0"
1240
+ }
1241
+ }
1242
+ ```
1243
+
1244
+ ```ruby
1245
+ class SidekiqConfig < Qonfig::DataSet
1246
+ # NOTE: file name should be described WITHOUT environment part (in file name attribute)
1247
+ expose_json 'config/sidekiq.json', via: :file_name, env: :staging # load from staging env
1248
+
1249
+ # NOTE: in rails-like application you can use this:
1250
+ expose_json 'config/sidekiq.json', via: :file_name, env: Rails.env
1251
+ end
1252
+
1253
+ config = SidekiqConfig.new
1254
+
1255
+ config.settings.web.username # => "staging_admin" (from sidekiq.staging.json)
1256
+ config.settings.web.password # => "staging_password" (from sidekiq.staging.json)
1257
+ ```
1258
+
1259
+ ---
1260
+
1100
1261
  ### Load from ENV
1101
1262
 
1102
1263
  - `:convert_values` (`false` by default):
@@ -1168,6 +1329,7 @@ config.settings['RUN_CI'] # => '1'
1168
1329
  ### Load from \_\_END\_\_
1169
1330
 
1170
1331
  - aka `load_from_self`
1332
+ - works with `YAML` format;
1171
1333
 
1172
1334
  ```ruby
1173
1335
  class Config < Qonfig::DataSet
@@ -1206,6 +1368,57 @@ connection_timeout:
1206
1368
 
1207
1369
  ---
1208
1370
 
1371
+ ### Expose \_\_END\_\_
1372
+
1373
+ - aka `expose_self`;
1374
+ - works in `expose_json` and `expose_yaml` manner, but with `__END__` instruction of the current file;
1375
+ - `env:` - your environment name (must be a type of `String`, `Symbol` or `Numeric`);
1376
+ - works with `YAML` format;
1377
+
1378
+ ```ruby
1379
+ class Config < Qonfig::DataSet
1380
+ expose_self env: :production
1381
+
1382
+ # NOTE: for Rails-like applications you can use this:
1383
+ expose_self env: Rails.env
1384
+ end
1385
+
1386
+ config = Config.new
1387
+
1388
+ config.settings.log # => true (from :production environment)
1389
+ config.settings.api_enabled # => true (from :production environment)
1390
+ config.settings.creds.user # => "D@iVeR" (from :production environment)
1391
+ config.settings.creds.password # => "test123" (from :production environment)
1392
+
1393
+ __END__
1394
+ default: &default
1395
+ log: false
1396
+ api_enabled: true
1397
+ creds:
1398
+ user: admin
1399
+ password: 1234
1400
+
1401
+ development:
1402
+ <<: *default
1403
+ log: true
1404
+
1405
+ test:
1406
+ <<: *default
1407
+ log: false
1408
+
1409
+ staging:
1410
+ <<: *default
1411
+
1412
+ production:
1413
+ <<: *default
1414
+ log: true
1415
+ creds:
1416
+ user: D@iVeR
1417
+ password: test123
1418
+ ```
1419
+
1420
+ ---
1421
+
1209
1422
  ### Save to JSON file
1210
1423
 
1211
1424
  - `#save_to_json` - represents config object as a json structure and saves it to a file:
@@ -1360,6 +1573,10 @@ Qonfig.plugins # => array of strings
1360
1573
  Qonfig.plugin(:plugin_name) # or Qonfig.plugin('plugin_name')
1361
1574
  ```
1362
1575
 
1576
+ Provided plugins:
1577
+
1578
+ - [toml](#plugins-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`)
1579
+
1363
1580
  ---
1364
1581
 
1365
1582
  ### Plugins: toml
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.14.0
5
+ class Qonfig::Commands::ExposeJSON < Qonfig::Commands::Base
6
+ # @return [Hash]
7
+ #
8
+ # @api private
9
+ # @since 0.14.0
10
+ EXPOSERS = { file_name: :file_name, env_key: :env_key }.freeze
11
+
12
+ # @return [Hash]
13
+ #
14
+ # @api private
15
+ # @since 0.14.0
16
+ EMPTY_JSON_DATA = {}.freeze
17
+
18
+ # @return [String]
19
+ #
20
+ # @api private
21
+ # @since 0.14.0
22
+ attr_reader :file_path
23
+
24
+ # @return [Boolean]
25
+ #
26
+ # @api private
27
+ # @since 0.14.0
28
+ attr_reader :strict
29
+
30
+ # @return [Symbol]
31
+ #
32
+ # @api private
33
+ # @since 0.14.0
34
+ attr_reader :via
35
+
36
+ # @return [Symbol, String]
37
+ #
38
+ # @api private
39
+ # @since 0.14.0
40
+ attr_reader :env
41
+
42
+ # @param file_path [String]
43
+ # @option strict [Boolean]
44
+ # @option via [Symbol]
45
+ # @option env [String, Symbol]
46
+ #
47
+ # @api private
48
+ # @since 0.14.0
49
+ def initialize(file_path, strict: true, via:, env:)
50
+ unless env.is_a?(Symbol) || env.is_a?(String) || env.is_a?(Numeric)
51
+ raise Qonfig::ArgumentError, ':env should be a string or a symbol'
52
+ end
53
+
54
+ raise Qonfig::ArgumentError, ':env should be provided' if env.to_s.empty?
55
+ raise Qonfig::ArgumentError, 'used :via is unsupported' unless EXPOSERS.key?(via)
56
+
57
+ @file_path = file_path
58
+ @strict = strict
59
+ @via = via
60
+ @env = env
61
+ end
62
+
63
+ # @param data_set [Qonfig::DataSet]
64
+ # @param settings [Qonfig::Settings]
65
+ # @return [void]
66
+ #
67
+ # @api private
68
+ # @since 0.14.0
69
+ def call(data_set, settings)
70
+ case via
71
+ when EXPOSERS[:file_name]
72
+ expose_file_name!(settings)
73
+ when EXPOSERS[:env_key]
74
+ expose_env_key!(settings)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ # @param settings [Qonfig::Settings]
81
+ # @return [void]
82
+ #
83
+ # @api private
84
+ # @since 0.14.0
85
+ # rubocop:disable Metrics/AbcSize
86
+ def expose_file_name!(settings)
87
+ # NOTE: transform file name (insert environment name into the file name)
88
+ # from: path/to/file/file_name.file_extension
89
+ # to: path/to/file/file_name.env_name.file_extension
90
+
91
+ pathname = Pathname.new(file_path)
92
+ dirname = pathname.dirname
93
+ extname = pathname.extname.to_s
94
+ basename = pathname.basename.to_s.sub!(extname, '')
95
+ envname = [env.to_s, extname].reject(&:empty?).join('')
96
+ envfile = [basename, envname].reject(&:empty?).join('.')
97
+ realfile = dirname.join(envfile).to_s
98
+
99
+ json_data = load_json_data(realfile)
100
+ json_based_settings = build_data_set_klass(json_data).new.settings
101
+
102
+ settings.__append_settings__(json_based_settings)
103
+ end
104
+ # rubocop:enable Metrics/AbcSize
105
+
106
+ # @param settings [Qonfig::Settings]
107
+ # @return [void]
108
+ #
109
+ # @raise [Qonfig::ExposeError]
110
+ # @raise [Qonfig::IncompatibleJSONStructureError]
111
+ #
112
+ # @api private
113
+ # @since 0.14.0
114
+ # rubocop:disable Metrics/AbcSize
115
+ def expose_env_key!(settings)
116
+ json_data = load_json_data(file_path)
117
+ json_data_slice = json_data[env] || json_data[env.to_s] || json_data[env.to_sym]
118
+ json_data_slice = EMPTY_JSON_DATA.dup if json_data_slice.nil? && !strict
119
+
120
+ raise(
121
+ Qonfig::ExposeError,
122
+ "#{file_path} file does not contain settings with <#{env}> environment key!"
123
+ ) unless json_data_slice
124
+
125
+ raise(
126
+ Qonfig::IncompatibleJSONStructureError,
127
+ 'JSON content should have a hash-like structure'
128
+ ) unless json_data_slice.is_a?(Hash)
129
+
130
+ json_based_settings = build_data_set_klass(json_data_slice).new.settings
131
+
132
+ settings.__append_settings__(json_based_settings)
133
+ end
134
+ # rubocop:enable Metrics/AbcSize
135
+
136
+ # @param file_path [String]
137
+ # @return [Hash]
138
+ #
139
+ # @raise [Qonfig::IncompatibleJSONStructureError]
140
+ #
141
+ # @api private
142
+ # @since 0.14.0
143
+ def load_json_data(file_path)
144
+ Qonfig::Loaders::JSON.load_file(file_path, fail_on_unexist: strict).tap do |json_data|
145
+ raise(
146
+ Qonfig::IncompatibleJSONStructureError,
147
+ 'JSON content should have a hash-like structure'
148
+ ) unless json_data.is_a?(Hash)
149
+ end
150
+ end
151
+
152
+ # @param json_data [Hash]
153
+ # @return [Class<Qonfig::DataSet>]
154
+ #
155
+ # @api private
156
+ # @since 0.14.0
157
+ def build_data_set_klass(json_data)
158
+ Qonfig::DataSet::ClassBuilder.build_from_hash(json_data)
159
+ end
160
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.14.0
5
+ class Qonfig::Commands::ExposeSelf < Qonfig::Commands::Base
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.14.0
10
+ attr_reader :caller_location
11
+
12
+ # @return [Symbol, String]
13
+ #
14
+ # @api private
15
+ # @since 0.14.0
16
+ attr_reader :env
17
+
18
+ # @param caller_location [String]
19
+ # @option env [String, Symbol]
20
+ #
21
+ # @api private
22
+ # @since 0.14.0
23
+ def initialize(caller_location, env:)
24
+ unless env.is_a?(Symbol) || env.is_a?(String)
25
+ raise Qonfig::ArgumentError, ':env should be a string or a symbol'
26
+ end
27
+
28
+ if env.to_s.empty?
29
+ raise Qonfig::ArgumentError, ':env should be provided'
30
+ end
31
+
32
+ @caller_location = caller_location
33
+ @env = env
34
+ end
35
+
36
+ # @param data_set [Qonfig::DataSet]
37
+ # @param settings [Qonfig::Settings]
38
+ # @return [void]
39
+ #
40
+ # @api private
41
+ # @since 0.14.0
42
+ def call(data_set, settings)
43
+ yaml_data = load_self_placed_yaml_data
44
+ yaml_data_slice = yaml_data[env] || yaml_data[env.to_s] || yaml_data[env.to_sym]
45
+
46
+ raise(
47
+ Qonfig::ExposeError,
48
+ "#{file_path} file does not contain settings with <#{env}> environment key!"
49
+ ) unless yaml_data_slice
50
+
51
+ raise(
52
+ Qonfig::IncompatibleYAMLStructureError,
53
+ 'YAML content should have a hash-like structure'
54
+ ) unless yaml_data_slice.is_a?(Hash)
55
+
56
+ yaml_based_settings = build_data_set_klass(yaml_data_slice).new.settings
57
+ settings.__append_settings__(yaml_based_settings)
58
+ end
59
+
60
+ private
61
+
62
+ # @return [Hash]
63
+ #
64
+ # @raise [Qonfig::SelfDataNotFound]
65
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
66
+ #
67
+ # @api private
68
+ # @since 0.14.0
69
+ def load_self_placed_yaml_data
70
+ caller_file = caller_location.split(':').first
71
+
72
+ raise(
73
+ Qonfig::SelfDataNotFoundError,
74
+ "Caller file does not exist! (location: #{caller_location})"
75
+ ) unless File.exist?(caller_file)
76
+
77
+ data_match = IO.read(caller_file).match(/\n__END__\n(?<end_data>.*)/m)
78
+ raise Qonfig::SelfDataNotFoundError, '__END__ data not found!' unless data_match
79
+
80
+ end_data = data_match[:end_data]
81
+ raise Qonfig::SelfDataNotFoundError, '__END__ data not found!' unless end_data
82
+
83
+ yaml_data = Qonfig::Loaders::YAML.load(end_data)
84
+ raise(
85
+ Qonfig::IncompatibleYAMLStructureError,
86
+ 'YAML content should have a hash-like structure'
87
+ ) unless yaml_data.is_a?(Hash)
88
+
89
+ yaml_data
90
+ end
91
+
92
+ # @param self_placed_yaml_data [Hash]
93
+ # @return [Class<Qonfig::DataSet>]
94
+ #
95
+ # @api private
96
+ # @since 0.14.0
97
+ def build_data_set_klass(self_placed_yaml_data)
98
+ Qonfig::DataSet::ClassBuilder.build_from_hash(self_placed_yaml_data)
99
+ end
100
+ end
@@ -97,7 +97,7 @@ class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
97
97
  realfile = dirname.join(envfile).to_s
98
98
 
99
99
  yaml_data = load_yaml_data(realfile)
100
- yaml_based_settings = build_data_set_class(yaml_data).new.settings
100
+ yaml_based_settings = build_data_set_klass(yaml_data).new.settings
101
101
 
102
102
  settings.__append_settings__(yaml_based_settings)
103
103
  end
@@ -127,7 +127,7 @@ class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
127
127
  'YAML content should have a hash-like structure'
128
128
  ) unless yaml_data_slice.is_a?(Hash)
129
129
 
130
- yaml_based_settings = build_data_set_class(yaml_data_slice).new.settings
130
+ yaml_based_settings = build_data_set_klass(yaml_data_slice).new.settings
131
131
 
132
132
  settings.__append_settings__(yaml_based_settings)
133
133
  end
@@ -154,7 +154,7 @@ class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
154
154
  #
155
155
  # @api private
156
156
  # @since 0.7.0
157
- def build_data_set_class(yaml_data)
157
+ def build_data_set_klass(yaml_data)
158
158
  Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
159
159
  end
160
160
  end
@@ -64,7 +64,7 @@ class Qonfig::Commands::LoadFromENV < Qonfig::Commands::Base
64
64
  def call(data_set, settings)
65
65
  env_data = extract_env_data
66
66
 
67
- env_based_settings = build_data_set_class(env_data).new.settings
67
+ env_based_settings = build_data_set_klass(env_data).new.settings
68
68
 
69
69
  settings.__append_settings__(env_based_settings)
70
70
  end
@@ -90,7 +90,7 @@ class Qonfig::Commands::LoadFromENV < Qonfig::Commands::Base
90
90
  #
91
91
  # @api private
92
92
  # @since 0.2.0
93
- def build_data_set_class(env_data)
93
+ def build_data_set_klass(env_data)
94
94
  Qonfig::DataSet::ClassBuilder.build_from_hash(env_data)
95
95
  end
96
96
  end
@@ -39,7 +39,7 @@ class Qonfig::Commands::LoadFromJSON < Qonfig::Commands::Base
39
39
  'JSON object should have a hash-like structure'
40
40
  ) unless json_data.is_a?(Hash)
41
41
 
42
- json_based_settings = build_data_set_class(json_data).new.settings
42
+ json_based_settings = build_data_set_klass(json_data).new.settings
43
43
 
44
44
  settings.__append_settings__(json_based_settings)
45
45
  end
@@ -51,7 +51,7 @@ class Qonfig::Commands::LoadFromJSON < Qonfig::Commands::Base
51
51
  #
52
52
  # @api private
53
53
  # @since 0.5.0
54
- def build_data_set_class(json_data)
54
+ def build_data_set_klass(json_data)
55
55
  Qonfig::DataSet::ClassBuilder.build_from_hash(json_data)
56
56
  end
57
57
  end
@@ -41,7 +41,7 @@ class Qonfig::Commands::LoadFromYAML < Qonfig::Commands::Base
41
41
  'YAML content should have a hash-like structure'
42
42
  ) unless yaml_data.is_a?(Hash)
43
43
 
44
- yaml_based_settings = build_data_set_class(yaml_data).new.settings
44
+ yaml_based_settings = build_data_set_klass(yaml_data).new.settings
45
45
 
46
46
  settings.__append_settings__(yaml_based_settings)
47
47
  end
@@ -53,7 +53,7 @@ class Qonfig::Commands::LoadFromYAML < Qonfig::Commands::Base
53
53
  #
54
54
  # @api private
55
55
  # @since 0.2.0
56
- def build_data_set_class(yaml_data)
56
+ def build_data_set_klass(yaml_data)
57
57
  Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
58
58
  end
59
59
  end
@@ -12,4 +12,6 @@ module Qonfig::Commands
12
12
  require_relative 'commands/load_from_self'
13
13
  require_relative 'commands/load_from_env'
14
14
  require_relative 'commands/expose_yaml'
15
+ require_relative 'commands/expose_json'
16
+ require_relative 'commands/expose_self'
15
17
  end
data/lib/qonfig/dsl.rb CHANGED
@@ -119,4 +119,28 @@ module Qonfig::DSL
119
119
  def expose_yaml(file_path, strict: true, via:, env:)
120
120
  commands << Qonfig::Commands::ExposeYAML.new(file_path, strict: strict, via: via, env: env)
121
121
  end
122
+
123
+ # @param file_path [String]
124
+ # @option strict [Boolean]
125
+ # @option via [Symbol]
126
+ # @option env [Symbol, String]
127
+ # @return [void]
128
+ #
129
+ # @api public
130
+ # @since 0.14.0
131
+ def expose_json(file_path, strict: true, via:, env:)
132
+ commands << Qonfig::Commands::ExposeJSON.new(file_path, strict: strict, via: via, env: env)
133
+ end
134
+
135
+ # @option env [Symbol, String]
136
+ # @return [void]
137
+ #
138
+ # @see Qonfig::Commands::LoadFromSelf
139
+ #
140
+ # @api public
141
+ # @since 0.14.0
142
+ def expose_self(env:)
143
+ caller_location = caller(1, 1).first
144
+ commands << Qonfig::Commands::ExposeSelf.new(caller_location, env: env)
145
+ end
122
146
  end
@@ -97,7 +97,7 @@ class Qonfig::Commands::ExposeTOML < Qonfig::Commands::Base
97
97
  realfile = dirname.join(envfile).to_s
98
98
 
99
99
  toml_data = load_toml_data(realfile)
100
- toml_based_settings = build_data_set_class(toml_data).new.settings
100
+ toml_based_settings = builde_data_set_klass(toml_data).new.settings
101
101
 
102
102
  settings.__append_settings__(toml_based_settings)
103
103
  end
@@ -121,7 +121,7 @@ class Qonfig::Commands::ExposeTOML < Qonfig::Commands::Base
121
121
  "#{file_path} file does not contain settings with <#{env}> environment key!"
122
122
  ) unless toml_data_slice
123
123
 
124
- toml_based_settings = build_data_set_class(toml_data_slice).new.settings
124
+ toml_based_settings = builde_data_set_klass(toml_data_slice).new.settings
125
125
 
126
126
  settings.__append_settings__(toml_based_settings)
127
127
  end
@@ -141,7 +141,7 @@ class Qonfig::Commands::ExposeTOML < Qonfig::Commands::Base
141
141
  #
142
142
  # @api private
143
143
  # @since 0.12.0
144
- def build_data_set_class(toml_data)
144
+ def builde_data_set_klass(toml_data)
145
145
  Qonfig::DataSet::ClassBuilder.build_from_hash(toml_data)
146
146
  end
147
147
  end
@@ -33,7 +33,7 @@ class Qonfig::Commands::LoadFromTOML < Qonfig::Commands::Base
33
33
  # @since 0.12.0
34
34
  def call(data_set, settings)
35
35
  toml_data = Qonfig::Loaders::TOML.load_file(file_path, fail_on_unexist: strict)
36
- toml_based_settings = build_data_set_class(toml_data).new.settings
36
+ toml_based_settings = build_data_set_klass(toml_data).new.settings
37
37
  settings.__append_settings__(toml_based_settings)
38
38
  end
39
39
 
@@ -44,7 +44,7 @@ class Qonfig::Commands::LoadFromTOML < Qonfig::Commands::Base
44
44
  #
45
45
  # @api private
46
46
  # @since 0.12.0
47
- def build_data_set_class(toml_data)
47
+ def build_data_set_klass(toml_data)
48
48
  Qonfig::DataSet::ClassBuilder.build_from_hash(toml_data)
49
49
  end
50
50
  end
@@ -25,9 +25,10 @@ module Qonfig::Settings::Builder
25
25
  # @api private
26
26
  # @since 0.13.0
27
27
  def build_mutation_callbacks(data_set)
28
+ validation_callback = proc { data_set.validate! }
29
+
28
30
  Qonfig::Settings::Callbacks.new.tap do |callbacks|
29
- # NOTE: validation callbacks
30
- callbacks.add(proc { data_set.validate! })
31
+ callbacks.add(validation_callback)
31
32
  end
32
33
  end
33
34
  end
@@ -13,6 +13,7 @@ class Qonfig::Settings::Callbacks
13
13
  # @since 0.13.0
14
14
  def initialize
15
15
  @callbacks = []
16
+ @lock = Mutex.new
16
17
  end
17
18
 
18
19
  # @return [void]
@@ -20,7 +21,7 @@ class Qonfig::Settings::Callbacks
20
21
  # @api private
21
22
  # @since 0.13.0
22
23
  def call
23
- callbacks.each(&:call)
24
+ thread_safe { callbacks.each(&:call) }
24
25
  end
25
26
 
26
27
  # @param callback [Proc, Qonfig::Settings::Callbacks, #call]
@@ -29,9 +30,8 @@ class Qonfig::Settings::Callbacks
29
30
  # @api private
30
31
  # @since 0.13.0
31
32
  def add(callback)
32
- callbacks << callback
33
+ thread_safe { callbacks << callback }
33
34
  end
34
- attr_reader :callback
35
35
 
36
36
  private
37
37
 
@@ -40,4 +40,12 @@ class Qonfig::Settings::Callbacks
40
40
  # @api private
41
41
  # @since 0.13.0
42
42
  attr_reader :callbacks
43
+
44
+ # @return [Any]
45
+ #
46
+ # @api private
47
+ # @since 0.14.0
48
+ def thread_safe(&block)
49
+ @lock.owned? ? yield : @lock.synchronize(&block)
50
+ end
43
51
  end
@@ -5,5 +5,5 @@ module Qonfig
5
5
  #
6
6
  # @api public
7
7
  # @since 0.1.0
8
- VERSION = '0.13.0'
8
+ VERSION = '0.14.0'
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qonfig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.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-08-13 00:00:00.000000000 Z
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coveralls
@@ -140,6 +140,8 @@ files:
140
140
  - lib/qonfig/commands/add_option.rb
141
141
  - lib/qonfig/commands/base.rb
142
142
  - lib/qonfig/commands/compose.rb
143
+ - lib/qonfig/commands/expose_json.rb
144
+ - lib/qonfig/commands/expose_self.rb
143
145
  - lib/qonfig/commands/expose_yaml.rb
144
146
  - lib/qonfig/commands/load_from_env.rb
145
147
  - lib/qonfig/commands/load_from_env/value_converter.rb