qonfig 0.10.0 → 0.11.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -1
  4. data/.rubocop.yml +1 -1
  5. data/.travis.yml +3 -3
  6. data/CHANGELOG.md +10 -0
  7. data/README.md +146 -1
  8. data/Rakefile +2 -0
  9. data/lib/qonfig/command_set.rb +53 -55
  10. data/lib/qonfig/commands/add_nested_option.rb +36 -40
  11. data/lib/qonfig/commands/add_option.rb +33 -37
  12. data/lib/qonfig/commands/base.rb +9 -13
  13. data/lib/qonfig/commands/compose.rb +29 -33
  14. data/lib/qonfig/commands/expose_yaml.rb +154 -158
  15. data/lib/qonfig/commands/load_from_env.rb +77 -79
  16. data/lib/qonfig/commands/load_from_json.rb +52 -56
  17. data/lib/qonfig/commands/load_from_self.rb +57 -61
  18. data/lib/qonfig/commands/load_from_yaml.rb +54 -58
  19. data/lib/qonfig/commands.rb +15 -0
  20. data/lib/qonfig/configurable.rb +88 -90
  21. data/lib/qonfig/data_set/class_builder.rb +17 -21
  22. data/lib/qonfig/data_set.rb +186 -138
  23. data/lib/qonfig/dsl.rb +106 -108
  24. data/lib/qonfig/{error.rb → exceptions.rb} +13 -1
  25. data/lib/qonfig/loaders/basic.rb +30 -32
  26. data/lib/qonfig/loaders/json.rb +16 -23
  27. data/lib/qonfig/loaders/yaml.rb +16 -23
  28. data/lib/qonfig/loaders.rb +9 -0
  29. data/lib/qonfig/plugins/abstract.rb +7 -11
  30. data/lib/qonfig/plugins/access_mixin.rb +21 -25
  31. data/lib/qonfig/plugins/registry.rb +120 -124
  32. data/lib/qonfig/plugins.rb +56 -54
  33. data/lib/qonfig/settings/builder.rb +10 -14
  34. data/lib/qonfig/settings/key_guard.rb +60 -64
  35. data/lib/qonfig/settings/lock.rb +53 -57
  36. data/lib/qonfig/settings.rb +392 -354
  37. data/lib/qonfig/uploaders/base.rb +18 -0
  38. data/lib/qonfig/uploaders/file.rb +55 -0
  39. data/lib/qonfig/uploaders/json.rb +35 -0
  40. data/lib/qonfig/uploaders/yaml.rb +93 -0
  41. data/lib/qonfig/uploaders.rb +10 -0
  42. data/lib/qonfig/version.rb +1 -1
  43. data/lib/qonfig.rb +4 -21
  44. data/qonfig.gemspec +1 -1
  45. metadata +13 -6
@@ -1,163 +1,159 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Qonfig
4
- module Commands
5
- # @api private
6
- # @since 0.7.0
7
- class ExposeYAML < Base
8
- # @return [Hash]
9
- #
10
- # @api private
11
- # @since 0.7.0
12
- EXPOSERS = { file_name: :file_name, env_key: :env_key }.freeze
13
-
14
- # @return [Hash]
15
- #
16
- # @api private
17
- # @since 0.7.0
18
- EMPTY_YAML_DATA = {}.freeze
19
-
20
- # @return [String]
21
- #
22
- # @api private
23
- # @since 0.7.0
24
- attr_reader :file_path
25
-
26
- # @return [Boolean]
27
- #
28
- # @api private
29
- # @since 0.7.0
30
- attr_reader :strict
31
-
32
- # @return [Symbol]
33
- #
34
- # @api private
35
- # @since 0.7.0
36
- attr_reader :via
37
-
38
- # @return [Symbol, String]
39
- #
40
- # @api private
41
- # @since 0.7.0
42
- attr_reader :env
43
-
44
- # @param file_path [String]
45
- # @option strict [Boolean]
46
- # @option via [Symbol]
47
- # @option env [String, Symbol]
48
- #
49
- # @api private
50
- # @since 0.7.0
51
- def initialize(file_path, strict: true, via:, env:)
52
- unless env.is_a?(Symbol) || env.is_a?(String) || env.is_a?(Numeric)
53
- raise Qonfig::ArgumentError, ':env should be a string or a symbol'
54
- end
55
-
56
- raise Qonfig::ArgumentError, ':env should be provided' if env.to_s.empty?
57
- raise Qonfig::ArgumentError, 'used :via is unsupported' unless EXPOSERS.key?(via)
58
-
59
- @file_path = file_path
60
- @strict = strict
61
- @via = via
62
- @env = env
63
- end
64
-
65
- # @param settings [Qonfig::Settings]
66
- # @return [void]
67
- #
68
- # @api private
69
- # @since 0.7.0
70
- def call(settings)
71
- case via
72
- when EXPOSERS[:file_name]
73
- expose_file_name!(settings)
74
- when EXPOSERS[:env_key]
75
- expose_env_key!(settings)
76
- end
77
- end
78
-
79
- private
80
-
81
- # @param settings [Qonfig::Settings]
82
- # @return [void]
83
- #
84
- # @api private
85
- # @since 0.7.0
86
- # rubocop:disable Metrics/AbcSize
87
- def expose_file_name!(settings)
88
- # NOTE: transform file name (insert environment name into the file name)
89
- # from: path/to/file/file_name.file_extension
90
- # to: path/to/file/file_name.env_name.file_extension
91
-
92
- pathname = Pathname.new(file_path)
93
- dirname = pathname.dirname
94
- extname = pathname.extname.to_s
95
- basename = pathname.basename.to_s.sub!(extname, '')
96
- envname = [env.to_s, extname].reject(&:empty?).join('')
97
- envfile = [basename, envname].reject(&:empty?).join('.')
98
- realfile = dirname.join(envfile).to_s
99
-
100
- yaml_data = load_yaml_data(realfile)
101
- yaml_based_settings = build_data_set_class(yaml_data).new.settings
102
-
103
- settings.__append_settings__(yaml_based_settings)
104
- end
105
- # rubocop:enable Metrics/AbcSize
106
-
107
- # @param settings [Qonfig::Settings]
108
- # @return [void]
109
- #
110
- # @raise [Qonfig::ExposeError]
111
- # @raise [Qonfig::IncompatibleYAMLStructureError]
112
- #
113
- # @api private
114
- # @since 0.7.0
115
- # rubocop:disable Metrics/AbcSize
116
- def expose_env_key!(settings)
117
- yaml_data = load_yaml_data(file_path)
118
- yaml_data_slice = yaml_data[env] || yaml_data[env.to_s] || yaml_data[env.to_sym]
119
- yaml_data_slice = EMPTY_YAML_DATA.dup if yaml_data_slice.nil? && !strict
120
-
121
- raise(
122
- Qonfig::ExposeError,
123
- "#{file_path} file does not contain settings with <#{env}> environment key!"
124
- ) unless yaml_data_slice
125
-
126
- raise(
127
- Qonfig::IncompatibleYAMLStructureError,
128
- 'YAML content should have a hash-like structure'
129
- ) unless yaml_data_slice.is_a?(Hash)
130
-
131
- yaml_based_settings = build_data_set_class(yaml_data_slice).new.settings
132
-
133
- settings.__append_settings__(yaml_based_settings)
134
- end
135
- # rubocop:enable Metrics/AbcSize
136
-
137
- # @param file_path [String]
138
- # @return [Hash]
139
- #
140
- # @raise [Qonfig::IncompatibleYAMLStructureError]
141
- #
142
- # @api private
143
- # @since 0.7.0
144
- def load_yaml_data(file_path)
145
- Qonfig::Loaders::YAML.load_file(file_path, fail_on_unexist: strict).tap do |yaml_data|
146
- raise(
147
- Qonfig::IncompatibleYAMLStructureError,
148
- 'YAML content should have a hash-like structure'
149
- ) unless yaml_data.is_a?(Hash)
150
- end
151
- end
152
-
153
- # @param yaml_data [Hash]
154
- # @return [Class<Qonfig::DataSet>]
155
- #
156
- # @api private
157
- # @since 0.7.0
158
- def build_data_set_class(yaml_data)
159
- Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
160
- end
3
+ # @api private
4
+ # @since 0.7.0
5
+ class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
6
+ # @return [Hash]
7
+ #
8
+ # @api private
9
+ # @since 0.7.0
10
+ EXPOSERS = { file_name: :file_name, env_key: :env_key }.freeze
11
+
12
+ # @return [Hash]
13
+ #
14
+ # @api private
15
+ # @since 0.7.0
16
+ EMPTY_YAML_DATA = {}.freeze
17
+
18
+ # @return [String]
19
+ #
20
+ # @api private
21
+ # @since 0.7.0
22
+ attr_reader :file_path
23
+
24
+ # @return [Boolean]
25
+ #
26
+ # @api private
27
+ # @since 0.7.0
28
+ attr_reader :strict
29
+
30
+ # @return [Symbol]
31
+ #
32
+ # @api private
33
+ # @since 0.7.0
34
+ attr_reader :via
35
+
36
+ # @return [Symbol, String]
37
+ #
38
+ # @api private
39
+ # @since 0.7.0
40
+ attr_reader :env
41
+
42
+ # @param file_path [String]
43
+ # @option strict [Boolean]
44
+ # @option via [Symbol]
45
+ # @option env [String, Symbol]
46
+ #
47
+ # @api private
48
+ # @since 0.7.0
49
+ def initialize(file_path, strict: true, via:, env:)
50
+ unless env.is_a?(Symbol) || env.is_a?(String) || env.is_a?(Numeric)
51
+ raise Qonfig::ArgumentError, ':env should be a string or a symbol'
161
52
  end
53
+
54
+ raise Qonfig::ArgumentError, ':env should be provided' if env.to_s.empty?
55
+ raise Qonfig::ArgumentError, 'used :via is unsupported' unless EXPOSERS.key?(via)
56
+
57
+ @file_path = file_path
58
+ @strict = strict
59
+ @via = via
60
+ @env = env
61
+ end
62
+
63
+ # @param settings [Qonfig::Settings]
64
+ # @return [void]
65
+ #
66
+ # @api private
67
+ # @since 0.7.0
68
+ def call(settings)
69
+ case via
70
+ when EXPOSERS[:file_name]
71
+ expose_file_name!(settings)
72
+ when EXPOSERS[:env_key]
73
+ expose_env_key!(settings)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # @param settings [Qonfig::Settings]
80
+ # @return [void]
81
+ #
82
+ # @api private
83
+ # @since 0.7.0
84
+ # rubocop:disable Metrics/AbcSize
85
+ def expose_file_name!(settings)
86
+ # NOTE: transform file name (insert environment name into the file name)
87
+ # from: path/to/file/file_name.file_extension
88
+ # to: path/to/file/file_name.env_name.file_extension
89
+
90
+ pathname = Pathname.new(file_path)
91
+ dirname = pathname.dirname
92
+ extname = pathname.extname.to_s
93
+ basename = pathname.basename.to_s.sub!(extname, '')
94
+ envname = [env.to_s, extname].reject(&:empty?).join('')
95
+ envfile = [basename, envname].reject(&:empty?).join('.')
96
+ realfile = dirname.join(envfile).to_s
97
+
98
+ yaml_data = load_yaml_data(realfile)
99
+ yaml_based_settings = build_data_set_class(yaml_data).new.settings
100
+
101
+ settings.__append_settings__(yaml_based_settings)
102
+ end
103
+ # rubocop:enable Metrics/AbcSize
104
+
105
+ # @param settings [Qonfig::Settings]
106
+ # @return [void]
107
+ #
108
+ # @raise [Qonfig::ExposeError]
109
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
110
+ #
111
+ # @api private
112
+ # @since 0.7.0
113
+ # rubocop:disable Metrics/AbcSize
114
+ def expose_env_key!(settings)
115
+ yaml_data = load_yaml_data(file_path)
116
+ yaml_data_slice = yaml_data[env] || yaml_data[env.to_s] || yaml_data[env.to_sym]
117
+ yaml_data_slice = EMPTY_YAML_DATA.dup if yaml_data_slice.nil? && !strict
118
+
119
+ raise(
120
+ Qonfig::ExposeError,
121
+ "#{file_path} file does not contain settings with <#{env}> environment key!"
122
+ ) unless yaml_data_slice
123
+
124
+ raise(
125
+ Qonfig::IncompatibleYAMLStructureError,
126
+ 'YAML content should have a hash-like structure'
127
+ ) unless yaml_data_slice.is_a?(Hash)
128
+
129
+ yaml_based_settings = build_data_set_class(yaml_data_slice).new.settings
130
+
131
+ settings.__append_settings__(yaml_based_settings)
132
+ end
133
+ # rubocop:enable Metrics/AbcSize
134
+
135
+ # @param file_path [String]
136
+ # @return [Hash]
137
+ #
138
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
139
+ #
140
+ # @api private
141
+ # @since 0.7.0
142
+ def load_yaml_data(file_path)
143
+ Qonfig::Loaders::YAML.load_file(file_path, fail_on_unexist: strict).tap do |yaml_data|
144
+ raise(
145
+ Qonfig::IncompatibleYAMLStructureError,
146
+ 'YAML content should have a hash-like structure'
147
+ ) unless yaml_data.is_a?(Hash)
148
+ end
149
+ end
150
+
151
+ # @param yaml_data [Hash]
152
+ # @return [Class<Qonfig::DataSet>]
153
+ #
154
+ # @api private
155
+ # @since 0.7.0
156
+ def build_data_set_class(yaml_data)
157
+ Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
162
158
  end
163
159
  end
@@ -1,97 +1,95 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Qonfig
4
- module Commands
5
- # @api private
6
- # @since 0.2.0
7
- class LoadFromENV < Base
8
- # @return [Boolean]
9
- #
10
- # @api private
11
- # @since 0.2.0
12
- attr_reader :convert_values
3
+ # @api private
4
+ # @since 0.2.0
5
+ class Qonfig::Commands::LoadFromENV < Qonfig::Commands::Base
6
+ require_relative 'load_from_env/value_converter'
13
7
 
14
- # @return [Regexp]
15
- #
16
- # @api private
17
- # @since 0.2.0
18
- attr_reader :prefix_pattern
8
+ # @return [Boolean]
9
+ #
10
+ # @api private
11
+ # @since 0.2.0
12
+ attr_reader :convert_values
19
13
 
20
- # @return [Boolean]
21
- #
22
- # @api private
23
- # @since 0.2.0
24
- attr_reader :trim_prefix
14
+ # @return [Regexp]
15
+ #
16
+ # @api private
17
+ # @since 0.2.0
18
+ attr_reader :prefix_pattern
25
19
 
26
- # @return [Regexp]
27
- #
28
- # @api private
29
- # @since 0.2.0
30
- attr_reader :trim_pattern
20
+ # @return [Boolean]
21
+ #
22
+ # @api private
23
+ # @since 0.2.0
24
+ attr_reader :trim_prefix
31
25
 
32
- # @option convert_values [Boolean]
33
- # @opion prefix [NilClass, String, Regexp]
34
- #
35
- # @raise [Qonfig::ArgumentError]
36
- #
37
- # @api private
38
- # @since 0.2.0
39
- def initialize(convert_values: false, prefix: nil, trim_prefix: false)
40
- unless convert_values.is_a?(FalseClass) || convert_values.is_a?(TrueClass)
41
- raise Qonfig::ArgumentError, ':convert_values option should be a boolean'
42
- end
26
+ # @return [Regexp]
27
+ #
28
+ # @api private
29
+ # @since 0.2.0
30
+ attr_reader :trim_pattern
43
31
 
44
- unless prefix.is_a?(NilClass) || prefix.is_a?(String) || prefix.is_a?(Regexp)
45
- raise Qonfig::ArgumentError, ':prefix option should be a nil / string / regexp'
46
- end
32
+ # @option convert_values [Boolean]
33
+ # @opion prefix [NilClass, String, Regexp]
34
+ #
35
+ # @raise [Qonfig::ArgumentError]
36
+ #
37
+ # @api private
38
+ # @since 0.2.0
39
+ def initialize(convert_values: false, prefix: nil, trim_prefix: false)
40
+ unless convert_values.is_a?(FalseClass) || convert_values.is_a?(TrueClass)
41
+ raise Qonfig::ArgumentError, ':convert_values option should be a boolean'
42
+ end
47
43
 
48
- unless trim_prefix.is_a?(FalseClass) || trim_prefix.is_a?(TrueClass)
49
- raise Qonfig::ArgumentError, ':trim_refix options should be a boolean'
50
- end
44
+ unless prefix.is_a?(NilClass) || prefix.is_a?(String) || prefix.is_a?(Regexp)
45
+ raise Qonfig::ArgumentError, ':prefix option should be a nil / string / regexp'
46
+ end
51
47
 
52
- @convert_values = convert_values
53
- @prefix_pattern = prefix.is_a?(Regexp) ? prefix : /\A#{Regexp.escape(prefix.to_s)}.*\z/m
54
- @trim_prefix = trim_prefix
55
- @trim_pattern = prefix.is_a?(Regexp) ? prefix : /\A(#{Regexp.escape(prefix.to_s)})/m
56
- end
48
+ unless trim_prefix.is_a?(FalseClass) || trim_prefix.is_a?(TrueClass)
49
+ raise Qonfig::ArgumentError, ':trim_refix options should be a boolean'
50
+ end
57
51
 
58
- # @param settings [Qonfig::Settings]
59
- # @return [void]
60
- #
61
- # @api private
62
- # @since 0.2.0
63
- def call(settings)
64
- env_data = extract_env_data
52
+ @convert_values = convert_values
53
+ @prefix_pattern = prefix.is_a?(Regexp) ? prefix : /\A#{Regexp.escape(prefix.to_s)}.*\z/m
54
+ @trim_prefix = trim_prefix
55
+ @trim_pattern = prefix.is_a?(Regexp) ? prefix : /\A(#{Regexp.escape(prefix.to_s)})/m
56
+ end
65
57
 
66
- env_based_settings = build_data_set_class(env_data).new.settings
58
+ # @param settings [Qonfig::Settings]
59
+ # @return [void]
60
+ #
61
+ # @api private
62
+ # @since 0.2.0
63
+ def call(settings)
64
+ env_data = extract_env_data
67
65
 
68
- settings.__append_settings__(env_based_settings)
69
- end
66
+ env_based_settings = build_data_set_class(env_data).new.settings
70
67
 
71
- private
68
+ settings.__append_settings__(env_based_settings)
69
+ end
72
70
 
73
- # @return [Hash]
74
- #
75
- # @api private
76
- # @since 0.2.0
77
- def extract_env_data
78
- ENV.each_with_object({}) do |(key, value), env_data|
79
- next unless key.match(prefix_pattern)
80
- key = key.sub(trim_pattern, '') if trim_prefix
81
- env_data[key] = value
82
- end.tap do |env_data|
83
- ValueConverter.convert_values!(env_data) if convert_values
84
- end
85
- end
71
+ private
86
72
 
87
- # @param env_data [Hash]
88
- # @return [Class<Qonfig::DataSet>]
89
- #
90
- # @api private
91
- # @since 0.2.0
92
- def build_data_set_class(env_data)
93
- Qonfig::DataSet::ClassBuilder.build_from_hash(env_data)
94
- end
73
+ # @return [Hash]
74
+ #
75
+ # @api private
76
+ # @since 0.2.0
77
+ def extract_env_data
78
+ ENV.each_with_object({}) do |(key, value), env_data|
79
+ next unless key.match(prefix_pattern)
80
+ key = key.sub(trim_pattern, '') if trim_prefix
81
+ env_data[key] = value
82
+ end.tap do |env_data|
83
+ ValueConverter.convert_values!(env_data) if convert_values
95
84
  end
96
85
  end
86
+
87
+ # @param env_data [Hash]
88
+ # @return [Class<Qonfig::DataSet>]
89
+ #
90
+ # @api private
91
+ # @since 0.2.0
92
+ def build_data_set_class(env_data)
93
+ Qonfig::DataSet::ClassBuilder.build_from_hash(env_data)
94
+ end
97
95
  end
@@ -1,60 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Qonfig
4
- module Commands
5
- # @api private
6
- # @since 0.5.0
7
- class LoadFromJSON < Base
8
- # @return [String]
9
- #
10
- # @api private
11
- # @since 0.5.0
12
- attr_reader :file_path
13
-
14
- # @return [Boolean]
15
- #
16
- # @api private
17
- # @sicne 0.5.0
18
- attr_reader :strict
19
-
20
- # @param file_path [String]
21
- # @option strict [Boolean]
22
- #
23
- # @api private
24
- # @since 0.5.0
25
- def initialize(file_path, strict: true)
26
- @file_path = file_path
27
- @strict = strict
28
- end
29
-
30
- # @param settings [Qonfig::Settings]
31
- # @return [void]
32
- #
33
- # @api private
34
- # @since 0.5.0
35
- def call(settings)
36
- json_data = Qonfig::Loaders::JSON.load_file(file_path, fail_on_unexist: strict)
37
-
38
- raise(
39
- Qonfig::IncompatibleJSONStructureError,
40
- 'JSON object should have a hash-like structure'
41
- ) unless json_data.is_a?(Hash)
42
-
43
- json_based_settings = build_data_set_class(json_data).new.settings
44
-
45
- settings.__append_settings__(json_based_settings)
46
- end
47
-
48
- private
49
-
50
- # @param json_data [Hash]
51
- # @return [Class<Qonfig::DataSet>]
52
- #
53
- # @api private
54
- # @since 0.5.0
55
- def build_data_set_class(json_data)
56
- Qonfig::DataSet::ClassBuilder.build_from_hash(json_data)
57
- end
58
- end
3
+ # @api private
4
+ # @since 0.5.0
5
+ class Qonfig::Commands::LoadFromJSON < Qonfig::Commands::Base
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.5.0
10
+ attr_reader :file_path
11
+
12
+ # @return [Boolean]
13
+ #
14
+ # @api private
15
+ # @sicne 0.5.0
16
+ attr_reader :strict
17
+
18
+ # @param file_path [String]
19
+ # @option strict [Boolean]
20
+ #
21
+ # @api private
22
+ # @since 0.5.0
23
+ def initialize(file_path, strict: true)
24
+ @file_path = file_path
25
+ @strict = strict
26
+ end
27
+
28
+ # @param settings [Qonfig::Settings]
29
+ # @return [void]
30
+ #
31
+ # @api private
32
+ # @since 0.5.0
33
+ def call(settings)
34
+ json_data = Qonfig::Loaders::JSON.load_file(file_path, fail_on_unexist: strict)
35
+
36
+ raise(
37
+ Qonfig::IncompatibleJSONStructureError,
38
+ 'JSON object should have a hash-like structure'
39
+ ) unless json_data.is_a?(Hash)
40
+
41
+ json_based_settings = build_data_set_class(json_data).new.settings
42
+
43
+ settings.__append_settings__(json_based_settings)
44
+ end
45
+
46
+ private
47
+
48
+ # @param json_data [Hash]
49
+ # @return [Class<Qonfig::DataSet>]
50
+ #
51
+ # @api private
52
+ # @since 0.5.0
53
+ def build_data_set_class(json_data)
54
+ Qonfig::DataSet::ClassBuilder.build_from_hash(json_data)
59
55
  end
60
56
  end