qonfig 0.0.0 → 0.12.0

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