qonfig 0.1.0 → 0.2.0

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