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.
- checksums.yaml +4 -4
- data/.rubocop.yml +24 -3
- data/CHANGELOG.md +47 -1
- data/README.md +472 -19
- data/lib/qonfig/command_set.rb +26 -4
- data/lib/qonfig/commands/add_nested_option.rb +14 -11
- data/lib/qonfig/commands/add_option.rb +8 -3
- data/lib/qonfig/commands/compose.rb +7 -0
- data/lib/qonfig/commands/load_from_env/value_converter.rb +84 -0
- data/lib/qonfig/commands/load_from_env.rb +97 -0
- data/lib/qonfig/commands/load_from_self.rb +77 -0
- data/lib/qonfig/commands/load_from_yaml.rb +62 -0
- data/lib/qonfig/configurable.rb +108 -0
- data/lib/qonfig/data_set/class_builder.rb +32 -0
- data/lib/qonfig/data_set.rb +96 -5
- data/lib/qonfig/dsl.rb +48 -7
- data/lib/qonfig/error.rb +47 -1
- data/lib/qonfig/loaders/yaml.rb +34 -0
- data/lib/qonfig/settings/builder.rb +22 -0
- data/lib/qonfig/settings/key_guard.rb +75 -0
- data/lib/qonfig/settings/lock.rb +64 -0
- data/lib/qonfig/settings.rb +215 -54
- data/lib/qonfig/version.rb +2 -2
- data/lib/qonfig.rb +13 -1
- data/qonfig.gemspec +3 -3
- metadata +18 -9
- data/lib/qonfig/settings_builder.rb +0 -20
data/lib/qonfig/data_set.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
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
|
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 =
|
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
|