qonfig 0.1.0 → 0.2.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.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qonfig
4
+ # rubocop:disable Metrics/ClassLength
5
+
4
6
  # @api private
5
7
  # @since 0.1.0
6
8
  class Settings
@@ -14,31 +16,32 @@ module Qonfig
14
16
  # @since 0.1.0
15
17
  def initialize
16
18
  @__options__ = {}
19
+ @__lock__ = Lock.new
17
20
  end
18
21
 
19
- # @param key [Symbol,String]
22
+ # @param key [Symbol, String]
20
23
  # @param value [Object]
21
24
  # @return [void]
22
25
  #
23
26
  # @api private
24
27
  # @since 0.1.0
25
28
  def __define_setting__(key, value)
26
- # :nocov:
27
- unless key.is_a?(Symbol) || key.is_a?(String)
28
- raise Qonfig::ArgumentError, 'Setting key should be a symbol or a string'
29
- end
30
- # :nocov:
29
+ __lock__.thread_safe_definition do
30
+ key = __indifferently_accessable_option_key__(key)
31
31
 
32
- case
33
- when !__options__.key?(key)
34
- __options__[key] = value
35
- when __options__[key].is_a?(Qonfig::Settings) && value.is_a?(Qonfig::Settings)
36
- __options__[key].__append_settings__(value)
37
- else
38
- __options__[key] = value
39
- end
32
+ __prevent_core_method_intersection__(key)
33
+
34
+ case
35
+ when !__options__.key?(key)
36
+ __options__[key] = value
37
+ when __options__[key].is_a?(Qonfig::Settings) && value.is_a?(Qonfig::Settings)
38
+ __options__[key].__append_settings__(value)
39
+ else
40
+ __options__[key] = value
41
+ end
40
42
 
41
- __define_accessor__(key)
43
+ __define_accessor__(key)
44
+ end
42
45
  end
43
46
 
44
47
  # @param settings [Qonfig::Settings]
@@ -47,22 +50,20 @@ module Qonfig
47
50
  # @api private
48
51
  # @since 0.1.0
49
52
  def __append_settings__(settings)
50
- settings.__options__.each_pair do |key, value|
51
- __define_setting__(key, value)
53
+ __lock__.thread_safe_merge do
54
+ settings.__options__.each_pair do |key, value|
55
+ __define_setting__(key, value)
56
+ end
52
57
  end
53
58
  end
54
59
 
55
- # @param key [Symbol,String]
60
+ # @param key [Symbol, String]
56
61
  # @return [Object]
57
62
  #
58
63
  # @api public
59
64
  # @since 0.1.0
60
65
  def [](key)
61
- unless __options__.key?(key)
62
- raise Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!"
63
- end
64
-
65
- __options__[key]
66
+ __lock__.thread_safe_access { __get_value__(key) }
66
67
  end
67
68
 
68
69
  # @param key [String, Symbol]
@@ -72,27 +73,33 @@ module Qonfig
72
73
  # @api public
73
74
  # @since 0.1.0
74
75
  def []=(key, value)
75
- unless __options__.key?(key)
76
- raise Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!"
77
- end
78
-
79
- if __options__.frozen?
80
- raise Qonfig::FrozenSettingsError, 'Can not modify frozen Settings'
81
- end
76
+ __lock__.thread_safe_access { __set_value__(key, value) }
77
+ end
82
78
 
83
- __options__[key] = value
79
+ # @param keys [Array<String, Symbol>]
80
+ # @return [Object]
81
+ #
82
+ # @api private
83
+ # @since 0.2.0
84
+ def __dig__(*keys)
85
+ __lock__.thread_safe_access { __deep_access__(*keys) }
84
86
  end
85
87
 
86
88
  # @return [Hash]
87
89
  #
88
- # @api public
90
+ # @api private
89
91
  # @since 0.1.0
90
92
  def __to_hash__
91
- __options__.dup.tap do |hash|
92
- __options__.each_pair do |key, value|
93
- hash[key] = value.is_a?(Qonfig::Settings) ? value.__to_hash__ : value
94
- end
95
- end
93
+ __lock__.thread_safe_access { __build_hash_representation__ }
94
+ end
95
+ alias_method :__to_h__, :__to_hash__
96
+
97
+ # @return [void]
98
+ #
99
+ # @api private
100
+ # @since 0.2.0
101
+ def __clear__
102
+ __lock__.thread_safe_access { __clear_option_values__ }
96
103
  end
97
104
 
98
105
  # @param method_name [String, Symbol]
@@ -100,17 +107,19 @@ module Qonfig
100
107
  # @param block [Proc]
101
108
  # @return [void]
102
109
  #
103
- # @api public
110
+ # @raise [Qonfig::UnknownSettingError]
111
+ #
112
+ # @api private
104
113
  # @since 0.1.0
105
114
  def method_missing(method_name, *arguments, &block)
106
115
  super
107
116
  rescue NoMethodError
108
- raise Qonfig::UnknownSettingError, "Setting with <#{method_name}> ley doesnt exist!"
117
+ raise Qonfig::UnknownSettingError, "Setting with <#{method_name}> key doesnt exist!"
109
118
  end
110
119
 
111
120
  # @return [Boolean]
112
121
  #
113
- # @api public
122
+ # @api private
114
123
  # @since 0.1.0
115
124
  def respond_to_missing?(method_name, include_private = false)
116
125
  # :nocov:
@@ -123,35 +132,187 @@ module Qonfig
123
132
  # @api private
124
133
  # @since 0.1.0
125
134
  def __freeze__
126
- __options__.freeze
135
+ __lock__.thread_safe_access do
136
+ __options__.freeze
127
137
 
128
- __options__.each_value do |value|
129
- value.__freeze__ if value.is_a?(Qonfig::Settings)
138
+ __options__.each_value do |value|
139
+ value.__freeze__ if value.is_a?(Qonfig::Settings)
140
+ end
130
141
  end
131
142
  end
132
143
 
144
+ # @return [Boolean]
145
+ #
146
+ # @api private
147
+ # @since 0.2.0
148
+ def __is_frozen__
149
+ __lock__.thread_safe_access { __options__.frozen? }
150
+ end
151
+
133
152
  private
134
153
 
135
- # @param key [Symbol,String]
154
+ # @return [void]
155
+ #
156
+ # @api private
157
+ # @since 0.2.0
158
+ def __clear_option_values__
159
+ __options__.each_pair do |key, value|
160
+ if value.is_a?(Qonfig::Settings)
161
+ value.__clear__
162
+ else
163
+ __options__[key] = nil
164
+ end
165
+ end
166
+ end
167
+
168
+ # @param key [String, Symbol]
136
169
  # @return [Object]
137
170
  #
171
+ # @raise [Qonfig::UnknownSettingError]
172
+ #
138
173
  # @api private
139
- # @since 0.1.0
140
- def __define_accessor__(key)
141
- begin
142
- singleton_class.send(:undef_method, key)
143
- rescue NameError
174
+ # @since 0.2.0
175
+ def __get_value__(key)
176
+ key = __indifferently_accessable_option_key__(key)
177
+
178
+ unless __options__.key?(key)
179
+ raise Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!"
144
180
  end
145
181
 
146
- begin
147
- singleton_class.send(:undef_method, "#{key}=")
148
- rescue NameError
182
+ __options__[key]
183
+ end
184
+
185
+ # @param key [String, Symbol]
186
+ # @param value [Object]
187
+ # @return [void]
188
+ #
189
+ # @raise [Qonfig::UnknownSettingError]
190
+ # @raise [Qonfig::FrozenSettingsError]
191
+ # @raise [Qonfig::AmbiguousSettingValueError]
192
+ #
193
+ # @api private
194
+ # @since 0.2.0
195
+ def __set_value__(key, value)
196
+ key = __indifferently_accessable_option_key__(key)
197
+
198
+ unless __options__.key?(key)
199
+ raise Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!"
200
+ end
201
+
202
+ if __options__.frozen?
203
+ raise Qonfig::FrozenSettingsError, 'Can not modify frozen settings'
204
+ end
205
+
206
+ if __options__[key].is_a?(Qonfig::Settings)
207
+ raise Qonfig::AmbiguousSettingValueError, 'Can not redefine option with nested options'
208
+ end
209
+
210
+ __options__[key] = value
211
+ end
212
+
213
+ # @param keys [Array<Symbol, String>]
214
+ # @option result [Object]
215
+ # @return [Object]
216
+ #
217
+ # @raise [Qonfig::ArgumentError]
218
+ # @raise [Qonfig::UnknownSettingError]
219
+ #
220
+ # @api private
221
+ # @since 0.2.0
222
+ def __deep_access__(*keys)
223
+ raise Qonfig::ArgumentError, 'Key list can not be empty' if keys.empty?
224
+
225
+ result = __get_value__(keys.first)
226
+ rest_keys = Array(keys[1..-1])
227
+
228
+ case
229
+ when rest_keys.empty?
230
+ result
231
+ when !result.is_a?(Qonfig::Settings)
232
+ raise(Qonfig::UnknownSettingError, 'Setting with required digging sequence does not exist!')
233
+ when result.is_a?(Qonfig::Settings)
234
+ result.__dig__(*rest_keys)
235
+ end
236
+ end
237
+
238
+ # @return [Qonfig::Settings::Lock]
239
+ #
240
+ # @api private
241
+ # @since 0.2.0
242
+ attr_reader :__lock__
243
+
244
+ # @param options_part [Hash]
245
+ # @return [Hash]
246
+ #
247
+ # @api private
248
+ # @since 0.2.0
249
+ def __build_hash_representation__(options_part = __options__)
250
+ options_part.each_with_object({}) do |(key, value), hash|
251
+ case
252
+ when value.is_a?(Hash)
253
+ hash[key] = __build_hash_representation__(value)
254
+ when value.is_a?(Qonfig::Settings)
255
+ hash[key] = value.__to_hash__
256
+ else
257
+ hash[key] = value
258
+ end
259
+ end
260
+ end
261
+
262
+ # @param key [Symbol, String]
263
+ # @return [void]
264
+ #
265
+ # @api private
266
+ # @since 0.1.0
267
+ def __define_accessor__(key)
268
+ define_singleton_method(key) do
269
+ self.[](key)
149
270
  end
150
271
 
151
- define_singleton_method(key) { self.[](key) }
152
272
  define_singleton_method("#{key}=") do |value|
153
273
  self.[]=(key, value)
154
- end unless __options__[key].is_a?(Qonfig::Settings)
274
+ end
275
+
276
+ define_singleton_method("#{key}?") do
277
+ !!self.[](key)
278
+ end
155
279
  end
280
+
281
+ # @param key [Symbol, String]
282
+ # @return [String]
283
+ #
284
+ # @raise [Qonfig::ArgumentError]
285
+ # @see Qonfig::Settings::KeyGuard
286
+ #
287
+ # @api private
288
+ # @since 0.2.0
289
+ def __indifferently_accessable_option_key__(key)
290
+ KeyGuard.new(key).prevent_incompatible_key_type!
291
+ key.to_s
292
+ end
293
+
294
+ # @param key [Symbol, String]
295
+ # @return [void]
296
+ #
297
+ # @raise [Qonfig::CoreMethodIntersectionError]
298
+ # @see Qonfig::Settings::KeyGuard
299
+ #
300
+ # @api private
301
+ # @since 0.2.0
302
+ def __prevent_core_method_intersection__(key)
303
+ KeyGuard.new(key).prevent_core_method_intersection!
304
+ end
305
+
306
+ # @return [Array<String>]
307
+ #
308
+ # @api private
309
+ # @since 0.2.0
310
+ CORE_METHODS = Array(
311
+ instance_methods(false) |
312
+ private_instance_methods(false) |
313
+ %i[super raise define_singleton_method]
314
+ ).map(&:to_s).freeze
156
315
  end
316
+
317
+ # rubocop:enable Metrics/ClassLength
157
318
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Qonfig
4
4
  # @api public
5
- # @since 0.1.0
6
- VERSION = '0.1.0'
5
+ # @since 0.2.0
6
+ VERSION = '0.2.0'
7
7
  end
data/lib/qonfig.rb CHANGED
@@ -1,14 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
4
+ require 'erb'
5
+
3
6
  module Qonfig
4
7
  require_relative 'qonfig/error'
8
+ require_relative 'qonfig/loaders/yaml'
5
9
  require_relative 'qonfig/commands/base'
6
10
  require_relative 'qonfig/commands/add_option'
7
11
  require_relative 'qonfig/commands/add_nested_option'
8
12
  require_relative 'qonfig/commands/compose'
13
+ require_relative 'qonfig/commands/load_from_yaml'
14
+ require_relative 'qonfig/commands/load_from_self'
15
+ require_relative 'qonfig/commands/load_from_env'
16
+ require_relative 'qonfig/commands/load_from_env/value_converter'
9
17
  require_relative 'qonfig/command_set'
10
18
  require_relative 'qonfig/settings'
11
- require_relative 'qonfig/settings_builder'
19
+ require_relative 'qonfig/settings/lock'
20
+ require_relative 'qonfig/settings/builder'
21
+ require_relative 'qonfig/settings/key_guard'
12
22
  require_relative 'qonfig/dsl'
13
23
  require_relative 'qonfig/data_set'
24
+ require_relative 'qonfig/data_set/class_builder'
25
+ require_relative 'qonfig/configurable'
14
26
  end
data/qonfig.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.summary = 'Config object'
15
15
  spec.description = 'Config. Defined as a class. Used as an instance. ' \
16
16
  'Support for inheritance and composition. ' \
17
- 'Command-style DSL. Lazy instantiation.' \
17
+ 'Lazy instantiation. Thread-safe. Command-style DSL. ' \
18
18
  'Extremely simple to define. Extremely simple to use. That\'s all.'
19
19
  spec.homepage = 'https://github.com/0exp/qonfig'
20
20
  spec.license = 'MIT'
@@ -30,9 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'coveralls', '~> 0.8'
31
31
  spec.add_development_dependency 'simplecov', '~> 0.14'
32
32
  spec.add_development_dependency 'simplecov-json', '~> 0.2'
33
- spec.add_development_dependency 'rubocop', '~> 0.55'
33
+ spec.add_development_dependency 'rubocop', '~> 0.57'
34
34
  spec.add_development_dependency 'rspec', '~> 3.7'
35
- spec.add_development_dependency 'rubocop-rspec', '~> 1.25'
35
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.26'
36
36
 
37
37
  spec.add_development_dependency 'bundler'
38
38
  spec.add_development_dependency 'rake'
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.1.0
4
+ version: 0.2.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: 2018-05-18 00:00:00.000000000 Z
11
+ date: 2018-06-07 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.55'
61
+ version: '0.57'
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.55'
68
+ version: '0.57'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.25'
89
+ version: '1.26'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.25'
96
+ version: '1.26'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: bundler
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -137,8 +137,8 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  description: Config. Defined as a class. Used as an instance. Support for inheritance
140
- and composition. Command-style DSL. Lazy instantiation.Extremely simple to define.
141
- Extremely simple to use. That's all.
140
+ and composition. Lazy instantiation. Thread-safe. Command-style DSL. Extremely simple
141
+ to define. Extremely simple to use. That's all.
142
142
  email:
143
143
  - iamdaiver@icloud.com
144
144
  executables: []
@@ -164,11 +164,20 @@ files:
164
164
  - lib/qonfig/commands/add_option.rb
165
165
  - lib/qonfig/commands/base.rb
166
166
  - lib/qonfig/commands/compose.rb
167
+ - lib/qonfig/commands/load_from_env.rb
168
+ - lib/qonfig/commands/load_from_env/value_converter.rb
169
+ - lib/qonfig/commands/load_from_self.rb
170
+ - lib/qonfig/commands/load_from_yaml.rb
171
+ - lib/qonfig/configurable.rb
167
172
  - lib/qonfig/data_set.rb
173
+ - lib/qonfig/data_set/class_builder.rb
168
174
  - lib/qonfig/dsl.rb
169
175
  - lib/qonfig/error.rb
176
+ - lib/qonfig/loaders/yaml.rb
170
177
  - lib/qonfig/settings.rb
171
- - lib/qonfig/settings_builder.rb
178
+ - lib/qonfig/settings/builder.rb
179
+ - lib/qonfig/settings/key_guard.rb
180
+ - lib/qonfig/settings/lock.rb
172
181
  - lib/qonfig/version.rb
173
182
  - qonfig.gemspec
174
183
  homepage: https://github.com/0exp/qonfig
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Qonfig
4
- # @api private
5
- # @since 0.1.0
6
- module SettingsBuilder
7
- class << self
8
- # @param [Qonfig::CommandSet]
9
- # @return [Qonfig::Settings]
10
- #
11
- # @ api private
12
- # @since 0.1.0
13
- def build(commands)
14
- Qonfig::Settings.new.tap do |settings|
15
- commands.each { |command| command.call(settings) }
16
- end
17
- end
18
- end
19
- end
20
- end