qonfig 0.1.0 → 0.2.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.
@@ -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