qonfig 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,10 +13,15 @@ module Qonfig
13
13
  # @since 0.1.0
14
14
  attr_reader :settings
15
15
 
16
+ # @param configurations [Proc]
17
+ #
16
18
  # @api public
17
19
  # @since 0.1.0
18
- def initialize
19
- @settings = Qonfig::SettingsBuilder.build(self.class.commands)
20
+ def initialize(&configurations)
21
+ @__access_lock__ = Mutex.new
22
+ @__definition_lock__ = Mutex.new
23
+
24
+ thread_safe_definition { load!(&configurations) }
20
25
  end
21
26
 
22
27
  # @return [void]
@@ -24,7 +29,29 @@ module Qonfig
24
29
  # @api public
25
30
  # @since 0.1.0
26
31
  def freeze!
27
- settings.__freeze__
32
+ thread_safe_access { settings.__freeze__ }
33
+ end
34
+
35
+ # @return [void]
36
+ #
37
+ # @api public
38
+ # @since 0.2.0
39
+ def frozen?
40
+ thread_safe_access { settings.__is_frozen__ }
41
+ end
42
+
43
+ # @param configurations [Proc]
44
+ # @return [void]
45
+ #
46
+ # @raise [Qonfig::FrozenSettingsError]
47
+ #
48
+ # @api public
49
+ # @since 0.2.0
50
+ def reload!(&configurations)
51
+ thread_safe_definition do
52
+ raise Qonfig::FrozenSettingsError, 'Frozen config can not be reloaded' if frozen?
53
+ load!(&configurations)
54
+ end
28
55
  end
29
56
 
30
57
  # @return [void]
@@ -32,7 +59,7 @@ module Qonfig
32
59
  # @api public
33
60
  # @since 0.1.0
34
61
  def configure
35
- yield(settings) if block_given?
62
+ thread_safe_access { yield(settings) if block_given? }
36
63
  end
37
64
 
38
65
  # @return [Hash]
@@ -40,8 +67,72 @@ module Qonfig
40
67
  # @api public
41
68
  # @since 0.1.0
42
69
  def to_h
43
- settings.__to_hash__
70
+ thread_safe_access { settings.__to_hash__ }
44
71
  end
45
72
  alias_method :to_hash, :to_h
73
+
74
+ # @param key [String, Symbol]
75
+ # @return [Object]
76
+ #
77
+ # @api public
78
+ # @since 0.2.0
79
+ def [](key)
80
+ thread_safe_access { settings[key] }
81
+ end
82
+
83
+ # @param keys [Array<String, Symbol>]
84
+ # @return [Object]
85
+ #
86
+ # @api public
87
+ # @since 0.2.0
88
+ def dig(*keys)
89
+ thread_safe_access { settings.__dig__(*keys) }
90
+ end
91
+
92
+ # @return [void]
93
+ #
94
+ # @api public
95
+ # @since 0.2.0
96
+ def clear!
97
+ thread_safe_access { settings.__clear__ }
98
+ end
99
+
100
+ private
101
+
102
+ # @return [Qonfig::Settings]
103
+ #
104
+ # @api private
105
+ # @since 0.2.0
106
+ def build_settings
107
+ Qonfig::Settings::Builder.build(self.class.commands.dup)
108
+ end
109
+
110
+ # @param configurations [Proc]
111
+ # @return [void]
112
+ #
113
+ # @api private
114
+ # @since 0.2.0
115
+ def load!(&configurations)
116
+ @settings = build_settings
117
+ configure(&configurations) if block_given?
118
+ end
119
+
120
+ # @param instructions [Proc]
121
+ # @return [Object]
122
+ #
123
+ # @api private
124
+ # @since 0.2.0
125
+ def thread_safe_access(&instructions)
126
+ @__access_lock__.synchronize(&instructions)
127
+ end
128
+
129
+ # @param instructions [Proc]
130
+ # @return [Object]
131
+ #
132
+ # @api private
133
+ # @since 0.2.0
134
+ def thread_safe_definition(&instructions)
135
+ @__definition_lock__.synchronize(&instructions)
136
+ end
46
137
  end
47
138
  end
data/lib/qonfig/dsl.rb CHANGED
@@ -13,12 +13,13 @@ module Qonfig
13
13
  def extended(child_klass)
14
14
  child_klass.instance_variable_set(:@commands, Qonfig::CommandSet.new)
15
15
 
16
- class << child_klass
16
+ child_klass.singleton_class.prepend(Module.new do
17
17
  def inherited(child_klass)
18
18
  child_klass.instance_variable_set(:@commands, Qonfig::CommandSet.new)
19
19
  child_klass.commands.concat(commands)
20
+ super
20
21
  end
21
- end
22
+ end)
22
23
  end
23
24
  end
24
25
 
@@ -30,18 +31,17 @@ module Qonfig
30
31
  @commands
31
32
  end
32
33
 
33
- # @param key [String,Symbol]
34
+ # @param key [Symbol, String]
34
35
  # @param initial_value [Object]
35
36
  # @param nested_settings [Proc]
36
37
  # @return [void]
37
38
  #
39
+ # @see Qonfig::Commands::AddNestedOption
40
+ # @see Qonfig::Commands::AddOption
41
+ #
38
42
  # @api public
39
43
  # @since 0.1.0
40
44
  def setting(key, initial_value = nil, &nested_settings)
41
- unless key.is_a?(Symbol) || key.is_a?(String)
42
- raise Qonfig::ArgumentError, 'Setting key should be a symbol or a string!'
43
- end
44
-
45
45
  if block_given?
46
46
  commands << Qonfig::Commands::AddNestedOption.new(key, nested_settings)
47
47
  else
@@ -52,10 +52,51 @@ module Qonfig
52
52
  # @param data_set_klass [Class{Qonfig::DataSet}]
53
53
  # @return [void]
54
54
  #
55
+ # @see Qonfig::Comamnds::Compose
56
+ #
55
57
  # @api private
56
58
  # @sine 0.1.0
57
59
  def compose(data_set_klass)
58
60
  commands << Qonfig::Commands::Compose.new(data_set_klass)
59
61
  end
62
+
63
+ # @param file_path [String]
64
+ # @option strict [Boolean]
65
+ # @return [void]
66
+ #
67
+ # @see Qonfig::Commands::LoadFromYAML
68
+ #
69
+ # @api public
70
+ # @since 0.2.0
71
+ def load_from_yaml(file_path, strict: true)
72
+ commands << Qonfig::Commands::LoadFromYAML.new(file_path, strict: strict)
73
+ end
74
+
75
+ # @return [void]
76
+ #
77
+ # @see Qonfig::Commands::LoadFromSelf
78
+ #
79
+ # @api public
80
+ # @since 0.2.0
81
+ def load_from_self
82
+ caller_location = caller(1, 1).first
83
+ commands << Qonfig::Commands::LoadFromSelf.new(caller_location)
84
+ end
85
+
86
+ # @option convert_values [Boolean]
87
+ # @option prefix [NilClass, String, Regexp]
88
+ # @return [void]
89
+ #
90
+ # @see Qonfig::Commands::LoadFromENV
91
+ #
92
+ # @api public
93
+ # @since 0.2.0
94
+ def load_from_env(convert_values: false, prefix: nil, trim_prefix: false)
95
+ commands << Qonfig::Commands::LoadFromENV.new(
96
+ convert_values: convert_values,
97
+ prefix: prefix,
98
+ trim_prefix: trim_prefix
99
+ )
100
+ end
60
101
  end
61
102
  end
data/lib/qonfig/error.rb CHANGED
@@ -9,11 +9,57 @@ module Qonfig
9
9
  # @since 0.1.0
10
10
  ArgumentError = Class.new(Error)
11
11
 
12
+ # @see Qonfig::Settings
13
+ #
12
14
  # @api public
13
15
  # @since 0.1.0
14
16
  UnknownSettingError = Class.new(Error)
15
17
 
18
+ # @see Qonfig::Settings
19
+ #
20
+ # @api public
21
+ # @since 0.2.0
22
+ AmbiguousSettingValueError = Class.new(Error)
23
+
24
+ # @see Qonfig::Settings
25
+ # @see Qonfig::Settings::KeyGuard
26
+ # @see Qonfig::Commands::AddOption
27
+ # @see Qonfig::Commands::AddNestedOption
28
+ #
29
+ # @api public
30
+ # @since 0.2.0
31
+ CoreMethodIntersectionError = Class.new(Error)
32
+
33
+ # @see Qonfig::Settings
34
+ # @see Qonfig::DataSet
35
+ #
16
36
  # @api public
17
37
  # @since 0.1.0
18
- FrozenSettingsError = Class.new(Error)
38
+ FrozenSettingsError = begin # rubocop:disable Naming/ConstantName
39
+ # :nocov:
40
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
41
+ Class.new(::FrozenError)
42
+ else
43
+ Class.new(::RuntimeError)
44
+ end
45
+ # :nocov:
46
+ end
47
+
48
+ # @see Qonfig::Commands::LoadFromYAML
49
+ #
50
+ # @api public
51
+ # @since 0.2.0
52
+ IncompatibleYAMLStructureError = Class.new(Error)
53
+
54
+ # @see Qonfig::Loaders::YAML
55
+ #
56
+ # @api public
57
+ # @since 0.2.0
58
+ FileNotFoundError = Class.new(Errno::ENOENT)
59
+
60
+ # @see Qonfig::Commands::LoadFromSelf
61
+ #
62
+ # @api public
63
+ # @since 0.2.0
64
+ SelfDataNotFoundError = Class.new(Error)
19
65
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ module Loaders
5
+ # @api private
6
+ # @since 0.2.0
7
+ module YAML
8
+ class << self
9
+ # @param data [String]
10
+ # @return [Object]
11
+ #
12
+ # @api private
13
+ # @since 0.2.0
14
+ def load(data)
15
+ ::YAML.load(ERB.new(data).result)
16
+ end
17
+
18
+ # @param file_path [String]
19
+ # @option fail_on_unexist [Boolean]
20
+ # @return [Object]
21
+ #
22
+ # @raise [Qonfig::FileNotFoundError]
23
+ #
24
+ # @api private
25
+ # @since 0.2.0
26
+ def load_file(file_path, fail_on_unexist: true)
27
+ load(::File.read(file_path))
28
+ rescue Errno::ENOENT => error
29
+ fail_on_unexist ? (raise Qonfig::FileNotFoundError, error.message) : load('{}')
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ class Settings
5
+ # @api private
6
+ # @since 0.2.0
7
+ module Builder
8
+ class << self
9
+ # @param [Qonfig::CommandSet]
10
+ # @return [Qonfig::Settings]
11
+ #
12
+ # @api private
13
+ # @since 0.2.0
14
+ def build(commands)
15
+ Qonfig::Settings.new.tap do |settings|
16
+ commands.each { |command| command.call(settings) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ class Settings
5
+ # @api private
6
+ # @since 0.2.0
7
+ class KeyGuard
8
+ class << self
9
+ # @param key [String, Symbol, Object]
10
+ # @return [void]
11
+ #
12
+ # @raise [Qonfig::ArgumentError]
13
+ # @raise [Qonfig::CoreMethodIntersectionError]
14
+ #
15
+ # @api private
16
+ # @since 0.2.0
17
+ def prevent_incomparabilities!(key)
18
+ new(key).prevent_incomparabilities!
19
+ end
20
+ end
21
+
22
+ # @return [String, Symbol, Object]
23
+ #
24
+ # @api private
25
+ # @sicne 0.2.0
26
+ attr_reader :key
27
+
28
+ # @param key [String, Symbol, Object]
29
+ #
30
+ # @api private
31
+ # @since 0.2.0
32
+ def initialize(key)
33
+ @key = key
34
+ end
35
+
36
+ # @return [void]
37
+ #
38
+ # @raise [Qonfig::ArgumentError]
39
+ # @raise [Qonfig::CoreMethodIntersectionError]
40
+ #
41
+ # @api private
42
+ # @since 0.2.0
43
+ def prevent_incomparabilities!
44
+ prevent_incompatible_key_type!
45
+ prevent_core_method_intersection!
46
+ end
47
+
48
+ # @return [void]
49
+ #
50
+ # @raise [Qonfig::ArgumentError]
51
+ #
52
+ # @api private
53
+ # @since 0.2.0
54
+ def prevent_incompatible_key_type!
55
+ raise(
56
+ Qonfig::ArgumentError,
57
+ 'Setting key should be a symbol or a string!'
58
+ ) unless key.is_a?(Symbol) || key.is_a?(String)
59
+ end
60
+
61
+ # @return [void]
62
+ #
63
+ # @raise [Qonfig::CoreMethodIntersectionError]
64
+ #
65
+ # @api private
66
+ # @since 0.2.0
67
+ def prevent_core_method_intersection!
68
+ raise(
69
+ Qonfig::CoreMethodIntersectionError,
70
+ "<#{key}> key can not be used since this is a private core method"
71
+ ) if Qonfig::Settings::CORE_METHODS.include?(key.to_s)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ class Settings
5
+ # @api private
6
+ # @since 0.2.0
7
+ class Lock
8
+ # @api private
9
+ # @since 0.2.0
10
+ def initialize
11
+ @definition_lock = Mutex.new
12
+ @access_lock = Mutex.new
13
+ @merge_lock = Mutex.new
14
+ end
15
+
16
+ # @param instructions [Proc]
17
+ # @return [Object]
18
+ #
19
+ # @api private
20
+ # @since 0.2.0
21
+ def thread_safe_definition(&instructions)
22
+ definition_lock.synchronize(&instructions)
23
+ end
24
+
25
+ # @param instructions [Proc]
26
+ # @return [Object]
27
+ #
28
+ # @api private
29
+ # @since 0.2.0
30
+ def thread_safe_access(&instructions)
31
+ access_lock.synchronize(&instructions)
32
+ end
33
+
34
+ # @param instructions [Proc]
35
+ # @return [Object]
36
+ #
37
+ # @api private
38
+ # @since 0.2.0
39
+ def thread_safe_merge(&instructions)
40
+ merge_lock.synchronize(&instructions)
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Mutex]
46
+ #
47
+ # @api private
48
+ # @since 0.2.0
49
+ attr_reader :definition_lock
50
+
51
+ # @return [Mutex]
52
+ #
53
+ # @api private
54
+ # @since 0.2.0
55
+ attr_reader :access_lock
56
+
57
+ # @return [Mutex]
58
+ #
59
+ # @api private
60
+ # @since 0.2.0
61
+ attr_reader :merge_lock
62
+ end
63
+ end
64
+ end