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.
@@ -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