qonfig 0.16.0 → 0.17.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/.travis.yml +6 -6
  4. data/CHANGELOG.md +17 -0
  5. data/README.md +630 -49
  6. data/gemfiles/with_external_deps.gemfile +2 -2
  7. data/lib/qonfig/commands/{add_nested_option.rb → definition/add_nested_option.rb} +1 -1
  8. data/lib/qonfig/commands/{add_option.rb → definition/add_option.rb} +1 -1
  9. data/lib/qonfig/commands/{compose.rb → definition/compose.rb} +1 -1
  10. data/lib/qonfig/commands/{expose_json.rb → definition/expose_json.rb} +1 -1
  11. data/lib/qonfig/commands/{expose_self.rb → definition/expose_self.rb} +1 -1
  12. data/lib/qonfig/commands/{expose_yaml.rb → definition/expose_yaml.rb} +1 -1
  13. data/lib/qonfig/commands/definition/load_from_env/value_converter.rb +82 -0
  14. data/lib/qonfig/commands/{load_from_env.rb → definition/load_from_env.rb} +1 -1
  15. data/lib/qonfig/commands/{load_from_json.rb → definition/load_from_json.rb} +1 -1
  16. data/lib/qonfig/commands/{load_from_self.rb → definition/load_from_self.rb} +1 -1
  17. data/lib/qonfig/commands/{load_from_yaml.rb → definition/load_from_yaml.rb} +1 -1
  18. data/lib/qonfig/commands/definition.rb +16 -0
  19. data/lib/qonfig/commands/instantiation/values_file.rb +171 -0
  20. data/lib/qonfig/commands/instantiation.rb +7 -0
  21. data/lib/qonfig/commands.rb +2 -10
  22. data/lib/qonfig/data_set/lock.rb +61 -4
  23. data/lib/qonfig/data_set.rb +151 -3
  24. data/lib/qonfig/dsl.rb +56 -16
  25. data/lib/qonfig/errors.rb +38 -11
  26. data/lib/qonfig/loaders/dynamic.rb +52 -0
  27. data/lib/qonfig/loaders/json.rb +6 -0
  28. data/lib/qonfig/loaders/yaml.rb +13 -0
  29. data/lib/qonfig/loaders.rb +3 -0
  30. data/lib/qonfig/plugins/toml/data_set.rb +13 -0
  31. data/lib/qonfig/plugins/toml/dsl.rb +6 -2
  32. data/lib/qonfig/plugins/toml/errors.rb +12 -0
  33. data/lib/qonfig/plugins/toml/loaders/dynamic.rb +31 -0
  34. data/lib/qonfig/plugins/toml/loaders/toml.rb +6 -0
  35. data/lib/qonfig/plugins/toml.rb +2 -0
  36. data/lib/qonfig/settings/builder.rb +1 -1
  37. data/lib/qonfig/settings.rb +21 -0
  38. data/lib/qonfig/validator/basic.rb +9 -1
  39. data/lib/qonfig/validator/builder/attribute_consistency.rb +29 -0
  40. data/lib/qonfig/validator/builder.rb +39 -14
  41. data/lib/qonfig/validator/dsl.rb +9 -1
  42. data/lib/qonfig/validator/method_based.rb +4 -2
  43. data/lib/qonfig/validator/predefined/common.rb +4 -2
  44. data/lib/qonfig/validator/predefined/registry.rb +0 -2
  45. data/lib/qonfig/validator/predefined/registry_control_mixin.rb +3 -2
  46. data/lib/qonfig/validator/proc_based.rb +4 -2
  47. data/lib/qonfig/version.rb +1 -1
  48. data/qonfig.gemspec +1 -1
  49. metadata +21 -15
  50. data/lib/qonfig/commands/load_from_env/value_converter.rb +0 -84
@@ -2,7 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- # @since 0.12.0 (qonfig: :toml plugin)
6
- gem 'toml-rb', '>= 1'
5
+ # @since 0.17.0 (qonfig: :toml plugin)
6
+ gem 'toml-rb', '>= 2'
7
7
 
8
8
  gemspec path: '..'
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- class Qonfig::Commands::AddNestedOption < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::AddNestedOption < Qonfig::Commands::Base
6
6
  # @return [Symbol, String]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- class Qonfig::Commands::AddOption < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::AddOption < Qonfig::Commands::Base
6
6
  # @return [Symbol, String]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- class Qonfig::Commands::Compose < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::Compose < Qonfig::Commands::Base
6
6
  # @return [Qonfig::DataSet]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.14.0
5
- class Qonfig::Commands::ExposeJSON < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::ExposeJSON < Qonfig::Commands::Base
6
6
  # @return [Hash]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.14.0
5
- class Qonfig::Commands::ExposeSelf < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::ExposeSelf < Qonfig::Commands::Base
6
6
  # @return [String, Symbol]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.7.0
5
- class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::ExposeYAML < Qonfig::Commands::Base
6
6
  # @return [Hash]
7
7
  #
8
8
  # @api private
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ module Qonfig::Commands::Definition::LoadFromENV::ValueConverter
6
+ # @return [Regexp]
7
+ #
8
+ # @api private
9
+ # @since 0.2.0
10
+ INTEGER_PATTERN = /\A\d+\z/.freeze
11
+
12
+ # @return [Regexp]
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ FLOAT_PATTERN = /\A\d+\.\d+\z/.freeze
17
+
18
+ # @return [Regexp]
19
+ #
20
+ # @api private
21
+ # @since 0.2.0
22
+ TRUE_PATTERN = /\A(t|true)\z/i.freeze
23
+
24
+ # @return [Regexp]
25
+ #
26
+ # @api private
27
+ # @since 0.2.0
28
+ FALSE_PATTERN = /\A(f|false)\z/i.freeze
29
+
30
+ # @return [Regexp]
31
+ #
32
+ # @api private
33
+ # @since 0.2.0
34
+ ARRAY_PATTERN = /\A[^'"].*\s*,\s*.*[^'"]\z/.freeze
35
+
36
+ # @return [Regexp]
37
+ #
38
+ # @api private
39
+ # @since 0.2.0
40
+ QUOTED_STRING_PATTERN = /\A['"].*['"]\z/.freeze
41
+
42
+ class << self
43
+ # @param env_data [Hash]
44
+ # @return [void]
45
+ #
46
+ # @api private
47
+ # @since 0.2.0
48
+ def convert_values!(env_data)
49
+ env_data.each_pair do |key, value|
50
+ env_data[key] = convert_value(value)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # @param value [Object]
57
+ # @return [Object]
58
+ #
59
+ # @api private
60
+ # @since 0.2.0
61
+ def convert_value(value)
62
+ return value unless value.is_a?(String)
63
+
64
+ case value
65
+ when INTEGER_PATTERN
66
+ Integer(value)
67
+ when FLOAT_PATTERN
68
+ Float(value)
69
+ when TRUE_PATTERN
70
+ true
71
+ when FALSE_PATTERN
72
+ false
73
+ when ARRAY_PATTERN
74
+ value.split(/\s*,\s*/).map(&method(:convert_value))
75
+ when QUOTED_STRING_PATTERN
76
+ value.gsub(/(\A['"]|['"]\z)/, '')
77
+ else
78
+ value
79
+ end
80
+ end
81
+ end
82
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.2.0
5
- class Qonfig::Commands::LoadFromENV < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::LoadFromENV < Qonfig::Commands::Base
6
6
  require_relative 'load_from_env/value_converter'
7
7
 
8
8
  # @return [Boolean]
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.5.0
5
- class Qonfig::Commands::LoadFromJSON < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::LoadFromJSON < Qonfig::Commands::Base
6
6
  # @return [String]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.2.0
5
- class Qonfig::Commands::LoadFromSelf < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::LoadFromSelf < Qonfig::Commands::Base
6
6
  # @return [String, Symbol]
7
7
  #
8
8
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.2.0
5
- class Qonfig::Commands::LoadFromYAML < Qonfig::Commands::Base
5
+ class Qonfig::Commands::Definition::LoadFromYAML < Qonfig::Commands::Base
6
6
  # @return [String]
7
7
  #
8
8
  # @api private
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.17.0
5
+ module Qonfig::Commands::Definition
6
+ require_relative 'definition/add_option'
7
+ require_relative 'definition/add_nested_option'
8
+ require_relative 'definition/compose'
9
+ require_relative 'definition/load_from_yaml'
10
+ require_relative 'definition/load_from_json'
11
+ require_relative 'definition/load_from_self'
12
+ require_relative 'definition/load_from_env'
13
+ require_relative 'definition/expose_yaml'
14
+ require_relative 'definition/expose_json'
15
+ require_relative 'definition/expose_self'
16
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.17.0
5
+ class Qonfig::Commands::Instantiation::ValuesFile < Qonfig::Commands::Base
6
+ # @return [Symbol]
7
+ #
8
+ # @api private
9
+ # @since 0.17.0
10
+ SELF_LOCATED_FILE_DEFINITION = :self
11
+
12
+ # @return [NilClass]
13
+ #
14
+ # @api private
15
+ # @since 0.17.0
16
+ NO_EXPOSE = nil
17
+
18
+ # @return [Boolean]
19
+ #
20
+ # @api private
21
+ # @since 0.17.0
22
+ DEFAULT_STRICT_BEHAVIOR = false
23
+
24
+ # @return [Symbol]
25
+ #
26
+ # @api private
27
+ # @since 0.17.0
28
+ DEFAULT_FORMAT = :dynamic
29
+
30
+ # @return [String, Symbol]
31
+ #
32
+ # @api private
33
+ # @since 0.17.0
34
+ attr_reader :file_path
35
+
36
+ # @return [String]
37
+ #
38
+ # @api private
39
+ # @since 0.17.0
40
+ attr_reader :caller_location
41
+
42
+ # @return [String, Symbol]
43
+ #
44
+ # @api private
45
+ # @since 0.17.0
46
+ attr_reader :format
47
+
48
+ # @return [Boolean]
49
+ #
50
+ # @api private
51
+ # @since 0.17.0
52
+ attr_reader :strict
53
+
54
+ # @return [NilClass, String, Symbol]
55
+ #
56
+ # @api private
57
+ # @since 0.17.0
58
+ attr_reader :expose
59
+
60
+ # @param file_path [String, Symbol]
61
+ # @param caller_location [String]
62
+ # @option format [String, Symbol]
63
+ # @option strict [Boolean]
64
+ # @option expose [NilClass, String, Symbol]
65
+ # @return [void]
66
+ #
67
+ # @api private
68
+ # @since 0.17.0
69
+ def initialize(
70
+ file_path,
71
+ caller_location,
72
+ format: DEFAULT_FORMAT,
73
+ strict: DEFAULT_STRICT_BEHAVIOR,
74
+ expose: NO_EXPOSE
75
+ )
76
+ prevent_incompatible_attributes!(file_path, format, strict, expose)
77
+
78
+ @file_path = file_path
79
+ @caller_location = caller_location
80
+ @format = format
81
+ @strict = strict
82
+ @expose = expose
83
+ end
84
+
85
+ # @param data_set [Qonfig::DataSet]
86
+ # @param settings [Qonfig::Settings]
87
+ # @return [void]
88
+ #
89
+ # @api private
90
+ # @since 0.17.0
91
+ def call(data_set, settings)
92
+ settings_values = load_settings_values
93
+ return unless settings_values
94
+ settings_values = (settings_values[expose.to_sym] || settings_values[expose.to_s]) if expose
95
+ data_set.configure(settings_values) if settings_values
96
+ end
97
+
98
+ private
99
+
100
+ # @return [Hash]
101
+ #
102
+ # @raise [Qonfig::IncompatibleDataStructureError]
103
+ #
104
+ # @api private
105
+ # @since 0.17.0
106
+ def load_settings_values
107
+ (file_path == SELF_LOCATED_FILE_DEFINITION) ? load_from_self : load_from_file
108
+ end
109
+
110
+ # @return [Hash]
111
+ #
112
+ # @api private
113
+ # @since 0.17.0
114
+ def load_from_file
115
+ Qonfig::Loaders.resolve(format).load_file(file_path, fail_on_unexist: strict).tap do |values|
116
+ raise(
117
+ Qonfig::IncompatibleDataStructureError,
118
+ 'Setting values must be a hash-like structure'
119
+ ) unless values.is_a?(Hash)
120
+ end
121
+ end
122
+
123
+ # @return [Hash]
124
+ #
125
+ # @api private
126
+ # @since 0.17.0
127
+ def load_from_self
128
+ end_data = Qonfig::Loaders::EndData.extract(caller_location)
129
+
130
+ Qonfig::Loaders.resolve(format).load(end_data).tap do |values|
131
+ raise(
132
+ Qonfig::IncompatibleDataStructureError,
133
+ 'Setting values must be a hash-like structure'
134
+ ) unless values.is_a?(Hash)
135
+ end
136
+ rescue Qonfig::SelfDataNotFoundError => error
137
+ raise(error) if strict
138
+ end
139
+
140
+ # @param file_path [String, Symbol]
141
+ # @param format [String, Symbol]
142
+ # @param strict [Boolean]
143
+ # @param expose [NilClass, String, Symbol]
144
+ # @return [void]
145
+ #
146
+ # @raise [Qonfig::ArgumentError]
147
+ # @raise [Qonfig::UnsupportedLoaderError]
148
+ #
149
+ # @api private
150
+ # @since 0.17.0
151
+ def prevent_incompatible_attributes!(file_path, format, strict, expose)
152
+ unless file_path.is_a?(String) || file_path == SELF_LOCATED_FILE_DEFINITION
153
+ raise Qonfig::ArgumentError, 'Incorrect file path'
154
+ end
155
+
156
+ unless format.is_a?(String) || format.is_a?(Symbol)
157
+ raise Qonfig::ArgumentError, 'Format should be a symbol or a string'
158
+ end
159
+
160
+ # NOTE: try to resolve corresponding loader (and fail if cannot be resolved)
161
+ Qonfig::Loaders.resolve(format)
162
+
163
+ unless expose.nil? || expose.is_a?(Symbol) || expose.is_a?(String)
164
+ raise Qonfig::ArgumentError, ':expose should be a string or a symbol (or nil)'
165
+ end
166
+
167
+ unless strict.is_a?(TrueClass) || strict.is_a?(FalseClass)
168
+ raise Qonfig::ArgumentError, ':strict should be a type of boolean'
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.17.0
5
+ module Qonfig::Commands::Instantiation
6
+ require_relative 'instantiation/values_file'
7
+ end
@@ -4,14 +4,6 @@
4
4
  # @since 0.1.0
5
5
  module Qonfig::Commands
6
6
  require_relative 'commands/base'
7
- require_relative 'commands/add_option'
8
- require_relative 'commands/add_nested_option'
9
- require_relative 'commands/compose'
10
- require_relative 'commands/load_from_yaml'
11
- require_relative 'commands/load_from_json'
12
- require_relative 'commands/load_from_self'
13
- require_relative 'commands/load_from_env'
14
- require_relative 'commands/expose_yaml'
15
- require_relative 'commands/expose_json'
16
- require_relative 'commands/expose_self'
7
+ require_relative 'commands/definition'
8
+ require_relative 'commands/instantiation'
17
9
  end
@@ -10,24 +10,48 @@ class Qonfig::DataSet::Lock
10
10
  def initialize
11
11
  @access_lock = Mutex.new
12
12
  @definition_lock = Mutex.new
13
+ @arbitary_lock = Mutex.new
13
14
  end
14
15
 
15
- # @param instructions [Proc]
16
+ # @param instructions [Block]
17
+ # @return [void]
18
+ #
19
+ # @api private
20
+ # @since 0.17.0
21
+ def with_arbitary_access(&instructions)
22
+ acquire_arbitary_lock(&instructions)
23
+ end
24
+
25
+ # @param instructions [Block]
16
26
  # @return [void]
17
27
  #
18
28
  # @api private
19
29
  # @since 0.13.0
20
30
  def thread_safe_access(&instructions)
21
- access_lock.owned? ? yield : access_lock.synchronize(&instructions)
31
+ if arbitary_lock.locked?
32
+ # :nocov:
33
+ # NOTE: covered in thread-based specs but simplecov can't gather this fact
34
+ with_arbitary_access { acquire_access_lock(&instructions) } # :nocov:
35
+ # :nocov:
36
+ else
37
+ acquire_access_lock(&instructions)
38
+ end
22
39
  end
23
40
 
24
- # @param instructions [Proc]
41
+ # @param instructions [Block]
25
42
  # @return [void]
26
43
  #
27
44
  # @api private
28
45
  # @since 0.13.0
29
46
  def thread_safe_definition(&instructions)
30
- definition_lock.owned? ? yield : definition_lock.synchronize(&instructions)
47
+ if arbitary_lock.locked?
48
+ # :nocov:
49
+ # NOTE: covered in thread-based specs but simplecov can't gather this fact
50
+ with_arbitary_access { acquire_definition_lock(&instructions) }
51
+ # :nocov:
52
+ else
53
+ acquire_definition_lock(&instructions)
54
+ end
31
55
  end
32
56
 
33
57
  private
@@ -43,4 +67,37 @@ class Qonfig::DataSet::Lock
43
67
  # @api private
44
68
  # @since 0.13.0
45
69
  attr_reader :definition_lock
70
+
71
+ # @return [Mutex]
72
+ #
73
+ # @api private
74
+ # @since 0.17.0
75
+ attr_reader :arbitary_lock
76
+
77
+ # @param instructions [Block]
78
+ # @return [void]
79
+ #
80
+ # @api private
81
+ # @since 0.17.0
82
+ def acquire_arbitary_lock(&instructions)
83
+ arbitary_lock.owned? ? yield : arbitary_lock.synchronize(&instructions)
84
+ end
85
+
86
+ # @param instructions [Block]
87
+ # @return [void]
88
+ #
89
+ # @api private
90
+ # @since 0.13.0
91
+ def acquire_access_lock(&instructions)
92
+ access_lock.owned? ? yield : access_lock.synchronize(&instructions)
93
+ end
94
+
95
+ # @param instructions [Block]
96
+ # @return [void]
97
+ #
98
+ # @api private
99
+ # @since 0.13.0
100
+ def acquire_definition_lock(&instructions)
101
+ definition_lock.owned? ? yield : definition_lock.synchronize(&instructions)
102
+ end
46
103
  end