qonfig 0.0.0 → 0.12.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -2
  3. data/.jrubyrc +1 -0
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +15 -0
  6. data/.travis.yml +43 -4
  7. data/CHANGELOG.md +121 -0
  8. data/Gemfile +4 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +1060 -19
  11. data/Rakefile +18 -4
  12. data/bin/console +5 -11
  13. data/bin/rspec +55 -0
  14. data/bin/setup +1 -0
  15. data/gemfiles/with_external_deps.gemfile +8 -0
  16. data/gemfiles/without_external_deps.gemfile +5 -0
  17. data/lib/qonfig.rb +22 -2
  18. data/lib/qonfig/command_set.rb +67 -0
  19. data/lib/qonfig/commands.rb +15 -0
  20. data/lib/qonfig/commands/add_nested_option.rb +45 -0
  21. data/lib/qonfig/commands/add_option.rb +41 -0
  22. data/lib/qonfig/commands/base.rb +12 -0
  23. data/lib/qonfig/commands/compose.rb +37 -0
  24. data/lib/qonfig/commands/expose_yaml.rb +159 -0
  25. data/lib/qonfig/commands/load_from_env.rb +95 -0
  26. data/lib/qonfig/commands/load_from_env/value_converter.rb +84 -0
  27. data/lib/qonfig/commands/load_from_json.rb +56 -0
  28. data/lib/qonfig/commands/load_from_self.rb +73 -0
  29. data/lib/qonfig/commands/load_from_yaml.rb +58 -0
  30. data/lib/qonfig/configurable.rb +116 -0
  31. data/lib/qonfig/data_set.rb +213 -0
  32. data/lib/qonfig/data_set/class_builder.rb +27 -0
  33. data/lib/qonfig/data_set/validator.rb +7 -0
  34. data/lib/qonfig/dsl.rb +122 -0
  35. data/lib/qonfig/errors.rb +111 -0
  36. data/lib/qonfig/loaders.rb +9 -0
  37. data/lib/qonfig/loaders/basic.rb +38 -0
  38. data/lib/qonfig/loaders/json.rb +24 -0
  39. data/lib/qonfig/loaders/yaml.rb +24 -0
  40. data/lib/qonfig/plugins.rb +65 -0
  41. data/lib/qonfig/plugins/abstract.rb +13 -0
  42. data/lib/qonfig/plugins/access_mixin.rb +38 -0
  43. data/lib/qonfig/plugins/registry.rb +125 -0
  44. data/lib/qonfig/plugins/toml.rb +26 -0
  45. data/lib/qonfig/plugins/toml/commands/expose_toml.rb +146 -0
  46. data/lib/qonfig/plugins/toml/commands/load_from_toml.rb +49 -0
  47. data/lib/qonfig/plugins/toml/data_set.rb +19 -0
  48. data/lib/qonfig/plugins/toml/dsl.rb +27 -0
  49. data/lib/qonfig/plugins/toml/loaders/toml.rb +24 -0
  50. data/lib/qonfig/plugins/toml/tomlrb_fixes.rb +92 -0
  51. data/lib/qonfig/plugins/toml/uploaders/toml.rb +25 -0
  52. data/lib/qonfig/settings.rb +457 -0
  53. data/lib/qonfig/settings/builder.rb +18 -0
  54. data/lib/qonfig/settings/key_guard.rb +71 -0
  55. data/lib/qonfig/settings/lock.rb +60 -0
  56. data/lib/qonfig/uploaders.rb +10 -0
  57. data/lib/qonfig/uploaders/base.rb +18 -0
  58. data/lib/qonfig/uploaders/file.rb +55 -0
  59. data/lib/qonfig/uploaders/json.rb +35 -0
  60. data/lib/qonfig/uploaders/yaml.rb +93 -0
  61. data/lib/qonfig/version.rb +7 -1
  62. data/qonfig.gemspec +29 -17
  63. metadata +122 -16
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class Qonfig::Commands::LoadFromENV < Qonfig::Commands::Base
6
+ require_relative 'load_from_env/value_converter'
7
+
8
+ # @return [Boolean]
9
+ #
10
+ # @api private
11
+ # @since 0.2.0
12
+ attr_reader :convert_values
13
+
14
+ # @return [Regexp]
15
+ #
16
+ # @api private
17
+ # @since 0.2.0
18
+ attr_reader :prefix_pattern
19
+
20
+ # @return [Boolean]
21
+ #
22
+ # @api private
23
+ # @since 0.2.0
24
+ attr_reader :trim_prefix
25
+
26
+ # @return [Regexp]
27
+ #
28
+ # @api private
29
+ # @since 0.2.0
30
+ attr_reader :trim_pattern
31
+
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
43
+
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
47
+
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
51
+
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
57
+
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
65
+
66
+ env_based_settings = build_data_set_class(env_data).new.settings
67
+
68
+ settings.__append_settings__(env_based_settings)
69
+ end
70
+
71
+ private
72
+
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
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
95
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ # @api private
5
+ # @since 0.2.0
6
+ module Commands::LoadFromENV::ValueConverter
7
+ # @return [Regexp]
8
+ #
9
+ # @api private
10
+ # @since 0.2.0
11
+ INTEGER_PATTERN = /\A\d+\z/.freeze
12
+
13
+ # @return [Regexp]
14
+ #
15
+ # @api private
16
+ # @since 0.2.0
17
+ FLOAT_PATTERN = /\A\d+\.\d+\z/.freeze
18
+
19
+ # @return [Regexp]
20
+ #
21
+ # @api private
22
+ # @since 0.2.0
23
+ TRUE_PATTERN = /\A(t|true)\z/i.freeze
24
+
25
+ # @return [Regexp]
26
+ #
27
+ # @api private
28
+ # @since 0.2.0
29
+ FALSE_PATTERN = /\A(f|false)\z/i.freeze
30
+
31
+ # @return [Regexp]
32
+ #
33
+ # @api private
34
+ # @since 0.2.0
35
+ ARRAY_PATTERN = /\A[^'"].*\s*,\s*.*[^'"]\z/.freeze
36
+
37
+ # @return [Regexp]
38
+ #
39
+ # @api private
40
+ # @since 0.2.0
41
+ QUOTED_STRING_PATTERN = /\A['"].*['"]\z/.freeze
42
+
43
+ class << self
44
+ # @param env_data [Hash]
45
+ # @return [void]
46
+ #
47
+ # @api private
48
+ # @since 0.2.0
49
+ def convert_values!(env_data)
50
+ env_data.each_pair do |key, value|
51
+ env_data[key] = convert_value(value)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # @param value [Object]
58
+ # @return [Object]
59
+ #
60
+ # @api private
61
+ # @since 0.2.0
62
+ def convert_value(value)
63
+ return value unless value.is_a?(String)
64
+
65
+ case value
66
+ when INTEGER_PATTERN
67
+ Integer(value)
68
+ when FLOAT_PATTERN
69
+ Float(value)
70
+ when TRUE_PATTERN
71
+ true
72
+ when FALSE_PATTERN
73
+ false
74
+ when ARRAY_PATTERN
75
+ value.split(/\s*,\s*/).map(&method(:convert_value))
76
+ when QUOTED_STRING_PATTERN
77
+ value.gsub(/(\A['"]|['"]\z)/, '')
78
+ else
79
+ value
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
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)
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class Qonfig::Commands::LoadFromSelf < Qonfig::Commands::Base
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.2.0
10
+ attr_reader :caller_location
11
+
12
+ # @param caller_location [String]
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ def initialize(caller_location)
17
+ @caller_location = caller_location
18
+ end
19
+
20
+ # @param settings [Qonfig::Settings]
21
+ # @return [void]
22
+ #
23
+ # @api private
24
+ # @since 0.2.0
25
+ def call(settings)
26
+ yaml_data = load_self_placed_yaml_data
27
+
28
+ yaml_based_settings = build_data_set_klass(yaml_data).new.settings
29
+
30
+ settings.__append_settings__(yaml_based_settings)
31
+ end
32
+
33
+ private
34
+
35
+ # @return [Hash]
36
+ #
37
+ # @raise [Qonfig::SelfDataNotFound]
38
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
39
+ #
40
+ # @api private
41
+ # @since 0.2.0
42
+ def load_self_placed_yaml_data
43
+ caller_file = caller_location.split(':').first
44
+
45
+ raise(
46
+ Qonfig::SelfDataNotFoundError,
47
+ "Caller file does not exist! (location: #{caller_location})"
48
+ ) unless File.exist?(caller_file)
49
+
50
+ data_match = IO.read(caller_file).match(/\n__END__\n(?<end_data>.*)/m)
51
+ raise Qonfig::SelfDataNotFoundError, '__END__ data not found!' unless data_match
52
+
53
+ end_data = data_match[:end_data]
54
+ raise Qonfig::SelfDataNotFoundError, '__END__ data not found!' unless end_data
55
+
56
+ yaml_data = Qonfig::Loaders::YAML.load(end_data)
57
+ raise(
58
+ Qonfig::IncompatibleYAMLStructureError,
59
+ 'YAML content should have a hash-like structure'
60
+ ) unless yaml_data.is_a?(Hash)
61
+
62
+ yaml_data
63
+ end
64
+
65
+ # @param self_placed_yaml_data [Hash]
66
+ # @return [Class<Qonfig::DataSet>]
67
+ #
68
+ # @api private
69
+ # @since 0.2.0
70
+ def build_data_set_klass(self_placed_yaml_data)
71
+ Qonfig::DataSet::ClassBuilder.build_from_hash(self_placed_yaml_data)
72
+ end
73
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class Qonfig::Commands::LoadFromYAML < Qonfig::Commands::Base
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.2.0
10
+ attr_reader :file_path
11
+
12
+ # @return [Boolean]
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ attr_reader :strict
17
+
18
+ # @param file_path [String]
19
+ # @option strict [Boolean]
20
+ #
21
+ # @api private
22
+ # @since 0.2.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
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
32
+ #
33
+ # @api private
34
+ # @since 0.2.0
35
+ def call(settings)
36
+ yaml_data = Qonfig::Loaders::YAML.load_file(file_path, fail_on_unexist: strict)
37
+
38
+ raise(
39
+ Qonfig::IncompatibleYAMLStructureError,
40
+ 'YAML content should have a hash-like structure'
41
+ ) unless yaml_data.is_a?(Hash)
42
+
43
+ yaml_based_settings = build_data_set_class(yaml_data).new.settings
44
+
45
+ settings.__append_settings__(yaml_based_settings)
46
+ end
47
+
48
+ private
49
+
50
+ # @param yaml_data [Hash]
51
+ # @return [Class<Qonfig::DataSet>]
52
+ #
53
+ # @api private
54
+ # @since 0.2.0
55
+ def build_data_set_class(yaml_data)
56
+ Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
57
+ end
58
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.2.0
5
+ module Qonfig::Configurable
6
+ class << self
7
+ # @param base_klass [Class]
8
+ # @return [void]
9
+ #
10
+ # @api private
11
+ # @since 0.2.0
12
+ def included(base_klass)
13
+ base_klass.instance_variable_set(:@__qonfig_access_lock__, Mutex.new)
14
+ base_klass.instance_variable_set(:@__qonfig_definition_lock__, Mutex.new)
15
+ base_klass.instance_variable_set(:@__qonfig_config_klass__, Class.new(Qonfig::DataSet))
16
+ base_klass.instance_variable_set(:@__qonfig_config__, nil)
17
+
18
+ base_klass.extend(ClassMethods)
19
+ base_klass.include(InstanceMethods)
20
+ base_klass.singleton_class.prepend(ClassInheritance)
21
+
22
+ super
23
+ end
24
+ end
25
+
26
+ # @api private
27
+ # @since 0.2.0
28
+ module ClassInheritance
29
+ # @param child_klass [Class]
30
+ # @return [void]
31
+ #
32
+ # @api private
33
+ # @since 0.2.0
34
+ def inherited(child_klass)
35
+ inherited_config_klass = Class.new(@__qonfig_config_klass__)
36
+
37
+ child_klass.instance_variable_set(:@__qonfig_definition_lock__, Mutex.new)
38
+ child_klass.instance_variable_set(:@__qonfig_access_lock__, Mutex.new)
39
+ child_klass.instance_variable_set(:@__qonfig_config_klass__, inherited_config_klass)
40
+ child_klass.instance_variable_set(:@__qonfig_config__, nil)
41
+
42
+ super
43
+ end
44
+ end
45
+
46
+ # @api private
47
+ # @since 0.2.0
48
+ module ClassMethods
49
+ # @param block [Proc]
50
+ # @return [void]
51
+ #
52
+ # @api public
53
+ # @since 0.2.0
54
+ def configuration(&block)
55
+ @__qonfig_definition_lock__.synchronize do
56
+ @__qonfig_config_klass__.instance_eval(&block) if block_given?
57
+ end
58
+ end
59
+
60
+ # @param options_map [Hash]
61
+ # @param block [Proc]
62
+ # @return [void]
63
+ #
64
+ # @api public
65
+ # @since 0.2.0
66
+ def configure(options_map = {}, &block)
67
+ @__qonfig_access_lock__.synchronize do
68
+ config.configure(options_map, &block)
69
+ end
70
+ end
71
+
72
+ # @return [Qonfig::DataSet]
73
+ #
74
+ # @api public
75
+ # @since 0.2.0
76
+ def config
77
+ @__qonfig_definition_lock__.synchronize do
78
+ @__qonfig_config__ ||= @__qonfig_config_klass__.new
79
+ end
80
+ end
81
+ end
82
+
83
+ # @api private
84
+ # @since 0.2.0
85
+ module InstanceMethods
86
+ # @return [Qonfig::DataSet]
87
+ #
88
+ # @api public
89
+ # @since 0.2.0
90
+ def config
91
+ self.class.instance_variable_get(:@__qonfig_definition_lock__).synchronize do
92
+ @__qonfig_config__ ||= self.class.instance_variable_get(:@__qonfig_config_klass__).new
93
+ end
94
+ end
95
+
96
+ # @return [Qonfig::DataSet]
97
+ #
98
+ # @api public
99
+ # @since 0.6.0
100
+ def shared_config
101
+ self.class.config
102
+ end
103
+
104
+ # @param options_map [Hash]
105
+ # @param block [Proc]
106
+ # @return [void]
107
+ #
108
+ # @api public
109
+ # @since 0.2.0
110
+ def configure(options_map = {}, &block)
111
+ self.class.instance_variable_get(:@__qonfig_access_lock__).synchronize do
112
+ config.configure(options_map, &block)
113
+ end
114
+ end
115
+ end
116
+ end