qonfig 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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