configurate 0.1.0 → 0.5.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.
@@ -1,23 +1,40 @@
1
- module Configurate; module Provider
2
- # This provider knows nothing upon initialization, however if you access
3
- # a setting ending with +=+ and give one argument to that call it remembers
4
- # that setting, stripping the +=+ and will return it on the next call
5
- # without +=+.
6
- class Dynamic < Base
7
- def initialize
8
- @settings = {}
9
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Configurate
4
+ module Provider
5
+ # This provider knows nothing upon initialization, however if you access
6
+ # a setting ending with +=+ and give one argument to that call it remembers
7
+ # that setting, stripping the +=+ and will return it on the next call
8
+ # without +=+. Sending +reset_dynamic!+ to it will make it forget all
9
+ # settings. Also assigning nil will have the effect of it forgetting
10
+ # a setting.
11
+ class Dynamic < Base
12
+ def lookup_path(setting_path, *args)
13
+ if setting_path.to_s == "reset_dynamic!"
14
+ @settings = nil
15
+ return true
16
+ end
17
+
18
+ if setting_path.setter? && !args.empty?
19
+ *root, key = setting_path.to_a
20
+ hash = root.inject(settings) {|hash, key| hash[key] }
21
+ hash[key] = extract_value(args)
22
+ end
23
+
24
+ Provider.lookup_in_hash setting_path, settings
25
+ end
10
26
 
11
- def lookup_path(setting_path, *args)
12
- if setting_path.is_setter? && args.length > 0
27
+ private
28
+
29
+ def settings
30
+ @settings ||= Hash.new {|hash, key| hash[key] = Hash.new(&hash.default_proc) }
31
+ end
32
+
33
+ def extract_value args
13
34
  value = args.first
14
35
  value = value.get if value.respond_to?(:_proxy?) && value._proxy?
15
- *root, key = setting_path.to_a
16
- hash = root.inject(@settings) {|hash, key| hash[key] ||= {} }
17
- hash[key] = value
36
+ value
18
37
  end
19
-
20
- Provider.lookup_in_hash setting_path, @settings
21
38
  end
22
39
  end
23
- end; end
40
+ end
@@ -1,17 +1,21 @@
1
- module Configurate; module Provider
2
- # This provider looks for settings in the environment.
3
- # For the setting +foo.bar_baz+ this provider will look for an
4
- # environment variable +FOO_BAR_BAZ+, joining all components of the
5
- # setting with underscores and upcasing the result.
6
- # If an value contains any commas (,) it's split at them and returned as array.
7
- class Env < Base
8
- def lookup_path(setting_path, *args)
9
- value = ENV[setting_path.join("_").upcase]
10
- unless value.nil?
11
- value = value.dup
12
- value = value.split(",") if value.include?(",")
1
+ # frozen_string_literal: true
2
+
3
+ module Configurate
4
+ module Provider
5
+ # This provider looks for settings in the environment.
6
+ # For the setting +foo.bar_baz+ this provider will look for an
7
+ # environment variable +FOO_BAR_BAZ+, joining all components of the
8
+ # setting with underscores and upcasing the result.
9
+ # If an value contains any commas (,) it's split at them and returned as array.
10
+ class Env < Base
11
+ def lookup_path(setting_path, *_args)
12
+ value = ENV[setting_path.join("_").upcase]
13
+ unless value.nil?
14
+ value = value.dup
15
+ value = value.split(",") if value.include?(",")
16
+ end
17
+ value
13
18
  end
14
- value
15
19
  end
16
20
  end
17
- end; end
21
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Configurate
4
+ module Provider
5
+ # This provider takes a nested string keyed hash and does nested lookups in it.
6
+ class StringHash < Base
7
+ # @param hash [::Hash] the string keyed hash to provide values from
8
+ # @param namespace [String] optionally set this as the root
9
+ # @param required [Boolean] whether or not to raise an error if
10
+ # the namespace, if given, is not found. Defaults to +true+.
11
+ # @param raise_on_missing [Boolean] whether to raise {Configurate::MissingSetting}
12
+ # if a setting can't be provided. Defaults to +false+.
13
+ # @param source [String] optional hint of what's the source of this configuration. Used in error messages.
14
+ # @raise [ArgumentError] if the namespace isn't found in the hash or the given object is not a hash
15
+ def initialize hash, namespace: nil, required: true, raise_on_missing: false, source: nil
16
+ raise ArgumentError, "Please provide a hash" unless hash.is_a?(Hash)
17
+
18
+ @required = required
19
+ @raise_on_missing = raise_on_missing
20
+ @source = source
21
+ @settings = root_from hash, namespace
22
+ end
23
+
24
+ def lookup_path setting_path, *_
25
+ Provider.lookup_in_hash(setting_path, @settings) {
26
+ raise MissingSetting.new "#{setting_path} is not a valid setting." if @raise_on_missing
27
+
28
+ nil
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def root_from hash, namespace
35
+ return hash if namespace.nil?
36
+
37
+ Provider.lookup_in_hash(SettingPath.new(namespace), hash) do
38
+ raise ArgumentError, "Namespace #{namespace} not found #{"in #{@source}" if @source}" if @required
39
+
40
+ warn "WARNING: Namespace #{namespace} not found #{"in #{@source}" if @source}"
41
+ nil
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "configurate"
4
+
5
+ module Configurate
6
+ module Provider
7
+ # This provider tries to open a TOML file and does nested lookups
8
+ # in it.
9
+ class TOML < StringHash
10
+ begin
11
+ require "toml-rb"
12
+ PARSER = TomlRB
13
+ rescue LoadError => e
14
+ require "tomlrb"
15
+ PARSER = Tomlrb
16
+ end
17
+
18
+ # @param file [String] the path to the file
19
+ # @param namespace [String] optionally set this as the root
20
+ # @param required [Boolean] whether or not to raise an error if
21
+ # the file or the namespace, if given, is not found. Defaults to +true+.
22
+ # @param raise_on_missing [Boolean] whether to raise {Configurate::MissingSetting}
23
+ # if a setting can't be provided. Defaults to +false+.
24
+ # @raise [ArgumentError] if the namespace isn't found in the file
25
+ # @raise [Errno:ENOENT] if the file isn't found
26
+ def initialize file, namespace: nil, required: true, raise_on_missing: false
27
+ super(PARSER.load_file(file),
28
+ namespace: namespace,
29
+ required: required,
30
+ raise_on_missing: raise_on_missing,
31
+ source: file
32
+ )
33
+ rescue Errno::ENOENT => e
34
+ warn "WARNING: Configuration file #{file} not found, ensure it's present"
35
+ raise e if required
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,37 +1,31 @@
1
- require 'yaml'
1
+ # frozen_string_literal: true
2
2
 
3
- module Configurate; module Provider
4
- # This provider tries to open a YAML file and does nested lookups
5
- # in it.
6
- class YAML < Base
7
- # @param file [String] the path to the file
8
- # @param opts [Hash]
9
- # @option opts [String] :namespace optionally set this as the root
10
- # @option opts [Boolean] :required wheter or not to raise an error if
11
- # the file or the namespace, if given, is not found. Defaults to +true+.
12
- # @raise [ArgumentError] if the namespace isn't found in the file
13
- # @raise [Errno:ENOENT] if the file isn't found
14
- def initialize file, opts = {}
15
- @settings = {}
16
- required = opts.delete(:required) { true }
3
+ require "yaml"
17
4
 
18
- @settings = ::YAML.load_file(file)
19
-
20
- namespace = opts.delete(:namespace)
21
- unless namespace.nil?
22
- @settings = Provider.lookup_in_hash(SettingPath.new(namespace), @settings) do
23
- raise ArgumentError, "Namespace #{namespace} not found in #{file}" if required
24
- $stderr.puts "WARNING: Namespace #{namespace} not found in #{file}"
25
- nil
26
- end
5
+ module Configurate
6
+ module Provider
7
+ # This provider tries to open a YAML file and does nested lookups
8
+ # in it.
9
+ class YAML < StringHash
10
+ # @param file [String] the path to the file
11
+ # @param namespace [String] optionally set this as the root
12
+ # @param required [Boolean] whether or not to raise an error if
13
+ # the file or the namespace, if given, is not found. Defaults to +true+.
14
+ # @param raise_on_missing [Boolean] whether to raise {Configurate::MissingSetting}
15
+ # if a setting can't be provided. Defaults to +false+.
16
+ # @raise [ArgumentError] if the namespace isn't found in the file
17
+ # @raise [Errno:ENOENT] if the file isn't found
18
+ def initialize file, namespace: nil, required: true, raise_on_missing: false
19
+ super(::YAML.load_file(file),
20
+ namespace: namespace,
21
+ required: required,
22
+ raise_on_missing: raise_on_missing,
23
+ source: file
24
+ )
25
+ rescue Errno::ENOENT => e
26
+ warn "WARNING: Configuration file #{file} not found, ensure it's present"
27
+ raise e if required
27
28
  end
28
- rescue Errno::ENOENT => e
29
- $stderr.puts "WARNING: Configuration file #{file} not found, ensure it's present"
30
- raise e if required
31
- end
32
-
33
- def lookup_path setting_path, *_
34
- Provider.lookup_in_hash(setting_path, @settings)
35
29
  end
36
30
  end
37
- end; end
31
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Configurate
2
4
  # Proxy object to support nested settings
3
5
  #
@@ -18,40 +20,66 @@ module Configurate
18
20
  @lookup_chain = lookup_chain
19
21
  @setting_path = SettingPath.new
20
22
  end
21
-
23
+
22
24
  def !
23
25
  !target
24
26
  end
25
-
26
- [:!=, :==, :eql?].each do |method|
27
+
28
+ %i[!= == eql? coerce].each do |method|
27
29
  define_method method do |other|
28
30
  target.public_send method, target_or_object(other)
29
31
  end
30
32
  end
31
-
33
+
34
+ {
35
+ to_int: :to_i,
36
+ to_hash: :to_h,
37
+ to_str: :to_s,
38
+ to_ary: :to_a
39
+ }.each do |method, converter|
40
+ define_method method do
41
+ value = target
42
+ return value.public_send converter if value.respond_to? converter
43
+
44
+ value.public_send method
45
+ end
46
+ end
47
+
32
48
  def _proxy?
33
49
  true
34
50
  end
35
-
51
+
36
52
  def respond_to? method, include_private=false
37
53
  method == :_proxy? || target_respond_to?(method, include_private)
38
54
  end
39
-
55
+
40
56
  def send *args, &block
41
57
  __send__(*args, &block)
42
58
  end
43
59
  alias_method :public_send, :send
44
-
60
+
61
+ def singleton_class
62
+ target.singleton_class
63
+ rescue ::TypeError
64
+ class << self
65
+ self
66
+ end
67
+ end
68
+
69
+ # rubocop:disable Style/MethodMissingSuper we handle all calls
70
+ # rubocop:disable Style/MissingRespondToMissing we override respond_to? instead
71
+
45
72
  def method_missing setting, *args, &block
46
73
  return target.public_send(setting, *args, &block) if target_respond_to? setting
47
74
 
48
75
  @setting_path << setting
49
-
50
- return target(*args) if @setting_path.is_question_or_setter?
51
-
76
+
77
+ return target(*args) if @setting_path.question_action_or_setter?
78
+
52
79
  self
53
80
  end
54
-
81
+ # rubocop:enable all
82
+
55
83
  # Get the setting at the current path, if found.
56
84
  # (see LookupChain#lookup)
57
85
  def target *args
@@ -60,9 +88,10 @@ module Configurate
60
88
  @lookup_chain.lookup @setting_path, *args
61
89
  end
62
90
  alias_method :get, :target
63
-
91
+
64
92
  private
65
- COMMON_KEY_NAMES = [:key, :method]
93
+
94
+ COMMON_KEY_NAMES = %i[key method].freeze
66
95
 
67
96
  def target_respond_to? setting, include_private=false
68
97
  return false if COMMON_KEY_NAMES.include? setting
@@ -1,4 +1,6 @@
1
- require 'forwardable'
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
2
4
 
3
5
  module Configurate
4
6
  # Class encapsulating the concept of a path to a setting
@@ -19,34 +21,40 @@ module Configurate
19
21
  def_delegators :@path, :empty?, :length, :size, :hsh
20
22
 
21
23
  # Whether the current path looks like a question or setter method
22
- def is_question_or_setter?
23
- is_question? || is_setter?
24
+ def question_action_or_setter?
25
+ question? || action? || setter?
24
26
  end
25
27
 
26
28
  # Whether the current path looks like a question method
27
- def is_question?
29
+ def question?
28
30
  @path.last.to_s.end_with?("?")
29
31
  end
30
32
 
33
+ # Whether the current path looks like an action method
34
+ def action?
35
+ @path.last.to_s.end_with?("!")
36
+ end
37
+
31
38
  # Whether the current path looks like a setter method
32
- def is_setter?
39
+ def setter?
33
40
  @path.last.to_s.end_with?("=")
34
41
  end
35
42
 
36
43
  def each
37
44
  return to_enum(:each) unless block_given?
45
+
38
46
  @path.each do |component|
39
47
  yield clean_special_characters(component)
40
48
  end
41
49
  end
42
50
 
43
- [:join, :first, :last, :shift, :pop].each do |method|
51
+ %i[join first last shift pop].each do |method|
44
52
  define_method method do |*args|
45
53
  clean_special_characters @path.public_send(method, *args)
46
54
  end
47
55
  end
48
56
 
49
- [:<<, :unshift, :push].each do |method|
57
+ %i[<< unshift push].each do |method|
50
58
  define_method method do |*args|
51
59
  @path.public_send method, *args.map(&:to_s)
52
60
  end
@@ -61,7 +69,11 @@ module Configurate
61
69
  end
62
70
 
63
71
  def inspect
64
- "<SettingPath:#{object_id.to_s(16)} path=#{to_s}:#{@path.object_id.to_s(16)} setter=#{is_setter?} question=#{is_question?}>"
72
+ "<SettingPath:#{object_id.to_s(16)} "\
73
+ "path=#{self}:#{@path.object_id.to_s(16)} "\
74
+ "question=#{question?} "\
75
+ "action=#{action?} "\
76
+ "setter=#{setter?}>"
65
77
  end
66
78
 
67
79
  private
@@ -1,8 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
2
4
 
3
5
  class InvalidConfigurationProvider; end
4
6
  class ValidConfigurationProvider
5
- def lookup(setting, *args); end
7
+ def lookup(_setting, *_args); end
6
8
  end
7
9
 
8
10
  describe Configurate::LookupChain do
@@ -71,34 +73,34 @@ describe Configurate::LookupChain do
71
73
 
72
74
  it "converts numbers to strings" do
73
75
  allow(@provider[0]).to receive(:lookup).and_return(5)
74
- expect(subject.lookup "foo").to eq "5"
76
+ expect(subject.lookup("foo")).to eq "5"
75
77
  end
76
78
 
77
79
  it "does not convert false to a string" do
78
80
  allow(@provider[0]).to receive(:lookup).and_return(false)
79
- expect(subject.lookup "enable").to be_falsey
81
+ expect(subject.lookup("enable")).to be_falsey
80
82
  end
81
83
 
82
84
  it "converts 'true' to true" do
83
85
  allow(@provider[0]).to receive(:lookup).and_return("true")
84
- expect(subject.lookup "enable").to be_truthy
86
+ expect(subject.lookup("enable")).to be_truthy
85
87
  end
86
88
 
87
89
  it "converts 'false' to false" do
88
90
  allow(@provider[0]).to receive(:lookup).and_return("false")
89
- expect(subject.lookup "enable").to be_falsey
91
+ expect(subject.lookup("enable")).to be_falsey
90
92
  end
91
93
 
92
94
  it "returns the value unchanged if it can't be converted" do
93
95
  value = double
94
96
  allow(value).to receive(:respond_to?).with(:to_s).and_return(false)
95
97
  allow(@provider[0]).to receive(:lookup).and_return(value)
96
- expect(subject.lookup "enable").to eq value
98
+ expect(subject.lookup("enable")).to eq value
97
99
  end
98
100
 
99
101
  it "returns nil if no value is found" do
100
- @provider.each { |p| allow(p).to receive(:lookup).and_raise(Configurate::SettingNotFoundError) }
101
- expect(subject.lookup "not.me").to be_nil
102
+ @provider.each {|p| allow(p).to receive(:lookup).and_raise(Configurate::SettingNotFoundError) }
103
+ expect(subject.lookup("not.me")).to be_nil
102
104
  end
103
105
  end
104
106
  end