qonfig 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,7 @@ module Qonfig
14
14
  # @since 0.1.0
15
15
  def initialize
16
16
  @commands = []
17
+ @access_lock = Mutex.new
17
18
  end
18
19
 
19
20
  # @param command [Qonfig::Commands::Base]
@@ -22,7 +23,7 @@ module Qonfig
22
23
  # @api private
23
24
  # @since 0.1.0
24
25
  def add_command(command)
25
- commands << command
26
+ thread_safe { commands << command }
26
27
  end
27
28
  alias_method :<<, :add_command
28
29
 
@@ -32,16 +33,37 @@ module Qonfig
32
33
  # @api private
33
34
  # @since 0.1.0
34
35
  def each(&block)
35
- block_given? ? commands.each(&block) : commands.each
36
+ thread_safe { block_given? ? commands.each(&block) : commands.each }
36
37
  end
37
38
 
38
39
  # @param command_set [Qonfig::CommandSet]
39
- # @return [Qonfig::CommandSet]
40
+ # @return [void]
40
41
  #
41
42
  # @api private
42
43
  # @since 0.1.0
43
44
  def concat(command_set)
44
- commands.concat(command_set.commands)
45
+ thread_safe { commands.concat(command_set.commands) }
46
+ end
47
+
48
+ # @return [Qonfig::CommandSet]
49
+ #
50
+ # @api private
51
+ # @since 0.2.0
52
+ def dup
53
+ thread_safe do
54
+ self.class.new.tap { |duplicate| duplicate.concat(self) }
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # @param block [Proc]
61
+ # @return [Object]
62
+ #
63
+ # @api private
64
+ # @since 0.2.0
65
+ def thread_safe(&block)
66
+ @access_lock.synchronize(&block)
45
67
  end
46
68
  end
47
69
  end
@@ -5,26 +5,33 @@ module Qonfig
5
5
  # @api private
6
6
  # @since 0.1.0
7
7
  class AddNestedOption < Base
8
- # @return [String,Symbol]
8
+ # @return [Symbol, String]
9
9
  #
10
10
  # @api private
11
11
  # @since 0.1.0
12
12
  attr_reader :key
13
13
 
14
- # @return [Proc]
14
+ # @return [Class<Qonfig::DataSet>]
15
15
  #
16
16
  # @api private
17
- # @since 0.1.0
18
- attr_reader :nested_definitions
17
+ # @since 0.2.0
18
+ attr_reader :nested_data_set_klass
19
19
 
20
- # @param key [String,Symbol]
20
+ # @param key [Symbol, String]
21
21
  # @param nested_definitions [Proc]
22
22
  #
23
+ # @raise [Qonfig::ArgumentError]
24
+ # @raise [Qonfig::CoreMethodIntersectionError]
25
+ #
23
26
  # @api private
24
27
  # @since 0.1.0
25
28
  def initialize(key, nested_definitions)
29
+ Qonfig::Settings::KeyGuard.prevent_incomparabilities!(key)
30
+
26
31
  @key = key
27
- @nested_definitions = nested_definitions
32
+ @nested_data_set_klass = Class.new(Qonfig::DataSet).tap do |data_set|
33
+ data_set.instance_eval(&nested_definitions)
34
+ end
28
35
  end
29
36
 
30
37
  # @param settings [Qonfig::Settings]
@@ -33,11 +40,7 @@ module Qonfig
33
40
  # @api private
34
41
  # @since 0.1.0
35
42
  def call(settings)
36
- nested_data_set = Class.new(Qonfig::DataSet).tap do |data_set|
37
- data_set.instance_eval(&nested_definitions)
38
- end
39
-
40
- nested_settings = nested_data_set.new.settings
43
+ nested_settings = nested_data_set_klass.new.settings
41
44
 
42
45
  settings.__define_setting__(key, nested_settings)
43
46
  end
@@ -5,7 +5,7 @@ module Qonfig
5
5
  # @api private
6
6
  # @since 0.1.0
7
7
  class AddOption < Base
8
- # @return [String,Symbol]
8
+ # @return [Symbol, String]
9
9
  #
10
10
  # @api private
11
11
  # @since 0.1.0
@@ -17,13 +17,18 @@ module Qonfig
17
17
  # @since 0.1.0
18
18
  attr_reader :value
19
19
 
20
- # @param key [String,Symbol]
20
+ # @param key [Symbol, String]
21
21
  # @param value [Object]
22
22
  #
23
+ # @raise [Qonfig::ArgumentError]
24
+ # @raise [Qonfig::CoreMethodIntersectionError]
25
+ #
23
26
  # @api private
24
27
  # @since 0.1.0
25
28
  def initialize(key, value)
26
- @key = key
29
+ Qonfig::Settings::KeyGuard.prevent_incomparabilities!(key)
30
+
31
+ @key = key
27
32
  @value = value
28
33
  end
29
34
 
@@ -13,9 +13,16 @@ module Qonfig
13
13
 
14
14
  # @param data_set_klass [Qonfig::DataSet]
15
15
  #
16
+ # @raise [Qonfig::ArgumentError]
17
+ #
16
18
  # @api private
17
19
  # @since 0.1.0
18
20
  def initialize(data_set_klass)
21
+ raise(
22
+ Qonfig::ArgumentError,
23
+ 'Composed config class should be a subtype of Qonfig::DataSet'
24
+ ) unless data_set_klass.is_a?(Class) && data_set_klass < Qonfig::DataSet
25
+
19
26
  @data_set_klass = data_set_klass
20
27
  end
21
28
 
@@ -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/
12
+
13
+ # @return [Regexp]
14
+ #
15
+ # @api private
16
+ # @since 0.2.0
17
+ FLOAT_PATTERN = /\A\d+\.\d+\z/
18
+
19
+ # @return [Regexp]
20
+ #
21
+ # @api private
22
+ # @since 0.2.0
23
+ TRUE_PATTERN = /\A(t|true)\z/i
24
+
25
+ # @return [Regexp]
26
+ #
27
+ # @api private
28
+ # @since 0.2.0
29
+ FALSE_PATTERN = /\A(f|false)\z/i
30
+
31
+ # @return [Regexp]
32
+ #
33
+ # @api private
34
+ # @since 0.2.0
35
+ ARRAY_PATTERN = /\A[^'"].*\s*,\s*.*[^'"]\z/
36
+
37
+ # @return [Regexp]
38
+ #
39
+ # @api private
40
+ # @since 0.2.0
41
+ QUOTED_STRING_PATTERN = /\A['"].*['"]\z/
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,97 @@
1
+ # frozen_string_literal: true
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
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
96
+ end
97
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ module Commands
5
+ # @api private
6
+ # @since 0.2.0
7
+ class LoadFromSelf < Base
8
+ # @return [String]
9
+ #
10
+ # @api private
11
+ # @since 0.2.0
12
+ attr_reader :caller_location
13
+
14
+ # @param caller_location [String]
15
+ #
16
+ # @api private
17
+ # @since 0.2.0
18
+ def initialize(caller_location)
19
+ @caller_location = caller_location
20
+ end
21
+
22
+ # @param settings [Qonfig::Settings]
23
+ # @return [void]
24
+ #
25
+ # @api private
26
+ # @since 0.2.0
27
+ def call(settings)
28
+ yaml_data = load_self_placed_yaml_data
29
+
30
+ yaml_based_settings = build_data_set_klass(yaml_data).new.settings
31
+
32
+ settings.__append_settings__(yaml_based_settings)
33
+ end
34
+
35
+ private
36
+
37
+ # @return [Hash]
38
+ #
39
+ # @raise [Qonfig::SelfDataNotFound]
40
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
41
+ #
42
+ # @api private
43
+ # @since 0.2.0
44
+ def load_self_placed_yaml_data
45
+ caller_file = caller_location.split(':').first
46
+
47
+ raise(
48
+ Qonfig::SelfDataNotFoundError,
49
+ "Caller file does not exist! (location: #{caller_location})"
50
+ ) unless File.exist?(caller_file)
51
+
52
+ data_match = IO.read(caller_file).match(/\n__END__\n(?<end_data>.*)/m)
53
+ raise Qonfig::SelfDataNotFoundError, '__END__ data not found!' unless data_match
54
+
55
+ end_data = data_match[:end_data]
56
+ raise Qonfig::SelfDataNotFoundError, '__END__ data not found!' unless end_data
57
+
58
+ yaml_data = Qonfig::Loaders::YAML.load(end_data)
59
+ raise(
60
+ Qonfig::IncompatibleYAMLStructureError,
61
+ 'YAML content should have a hash-like structure'
62
+ ) unless yaml_data.is_a?(Hash)
63
+
64
+ yaml_data
65
+ end
66
+
67
+ # @param self_placed_yaml_data [Hash]
68
+ # @return [Class<Qonfig::DataSet>]
69
+ #
70
+ # @api private
71
+ # @since 0.2.0
72
+ def build_data_set_klass(self_placed_yaml_data)
73
+ Qonfig::DataSet::ClassBuilder.build_from_hash(self_placed_yaml_data)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ module Commands
5
+ # @api private
6
+ # @since 0.2.0
7
+ class LoadFromYAML < Base
8
+ # @return [String]
9
+ #
10
+ # @api private
11
+ # @since 0.2.0
12
+ attr_reader :file_path
13
+
14
+ # @return [Boolean]
15
+ #
16
+ # @api private
17
+ # @since 0.2.0
18
+ attr_reader :strict
19
+
20
+ # @param file_path [String]
21
+ # @option strict [Boolean]
22
+ #
23
+ # @api private
24
+ # @since 0.2.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
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
34
+ #
35
+ # @api private
36
+ # @since 0.2.0
37
+ def call(settings)
38
+ yaml_data = Qonfig::Loaders::YAML.load_file(file_path, fail_on_unexist: strict)
39
+
40
+ raise(
41
+ Qonfig::IncompatibleYAMLStructureError,
42
+ 'YAML content should have a hash-like structure'
43
+ ) unless yaml_data.is_a?(Hash)
44
+
45
+ yaml_based_settings = build_data_set_class(yaml_data).new.settings
46
+
47
+ settings.__append_settings__(yaml_based_settings)
48
+ end
49
+
50
+ private
51
+
52
+ # @param yaml_data [Hash]
53
+ # @return [Class<Qonfig::DataSet>]
54
+ #
55
+ # @api private
56
+ # @since 0.2.0
57
+ def build_data_set_class(yaml_data)
58
+ Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ # @api public
5
+ # @since 0.2.0
6
+ module Configurable
7
+ class << self
8
+ # @param base_klass [Class]
9
+ # @return [void]
10
+ #
11
+ # @api private
12
+ # @since 0.2.0
13
+ def included(base_klass)
14
+ base_klass.instance_variable_set(:@__qonfig_access_lock__, Mutex.new)
15
+ base_klass.instance_variable_set(:@__qonfig_definition_lock__, Mutex.new)
16
+ base_klass.instance_variable_set(:@__qonfig_config_klass__, Class.new(Qonfig::DataSet))
17
+ base_klass.instance_variable_set(:@__qonfig_config__, nil)
18
+
19
+ base_klass.extend(ClassMethods)
20
+ base_klass.include(InstanceMethods)
21
+ base_klass.singleton_class.prepend(ClassInheritance)
22
+
23
+ super
24
+ end
25
+ end
26
+
27
+ # @api private
28
+ # @since 0.2.0
29
+ module ClassInheritance
30
+ # @param child_klass [Class]
31
+ # @return [void]
32
+ #
33
+ # @api private
34
+ # @since 0.2.0
35
+ def inherited(child_klass)
36
+ inherited_config_klass = Class.new(@__qonfig_config_klass__)
37
+
38
+ child_klass.instance_variable_set(:@__qonfig_definition_lock__, Mutex.new)
39
+ child_klass.instance_variable_set(:@__qonfig_access_lock__, Mutex.new)
40
+ child_klass.instance_variable_set(:@__qonfig_config_klass__, inherited_config_klass)
41
+ child_klass.instance_variable_set(:@__qonfig_config__, nil)
42
+
43
+ super
44
+ end
45
+ end
46
+
47
+ # @api private
48
+ # @since 0.2.0
49
+ module ClassMethods
50
+ # @param block [Proc]
51
+ # @return [void]
52
+ #
53
+ # @api public
54
+ # @since 0.2.0
55
+ def configuration(&block)
56
+ @__qonfig_definition_lock__.synchronize do
57
+ @__qonfig_config_klass__.instance_eval(&block) if block_given?
58
+ end
59
+ end
60
+
61
+ # @param block [Proc]
62
+ # @return [void]
63
+ #
64
+ # @api public
65
+ # @since 0.2.0
66
+ def configure(&block)
67
+ @__qonfig_access_lock__.synchronize do
68
+ config.configure(&block) if block_given?
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
+ # @param block [Proc]
97
+ # @return [void]
98
+ #
99
+ # @api public
100
+ # @since 0.2.0
101
+ def configure(&block)
102
+ self.class.instance_variable_get(:@__qonfig_access_lock__).synchronize do
103
+ config.configure(&block) if block_given?
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qonfig
4
+ class DataSet
5
+ # @api private
6
+ # @since 0.2.0
7
+ module ClassBuilder
8
+ class << self
9
+ # @param hash [Hash]
10
+ # @return [Class<Qonfig::DataSet>]
11
+ #
12
+ # @see Qonfig::DataSet
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ def build_from_hash(hash)
17
+ Class.new(Qonfig::DataSet).tap do |data_set_klass|
18
+ hash.each_pair do |key, value|
19
+ if value.is_a?(Hash)
20
+ sub_data_set_klass = build_from_hash(value)
21
+
22
+ data_set_klass.setting(key) { compose sub_data_set_klass }
23
+ else
24
+ data_set_klass.setting key, value
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end