qonfig 0.16.0 → 0.17.0

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