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.
- checksums.yaml +5 -5
- data/Changelog.md +30 -0
- data/README.md +96 -20
- data/lib/configurate.rb +26 -15
- data/lib/configurate/lookup_chain.rb +19 -17
- data/lib/configurate/provider.rb +34 -27
- data/lib/configurate/provider/dynamic.rb +34 -17
- data/lib/configurate/provider/env.rb +18 -14
- data/lib/configurate/provider/string_hash.rb +46 -0
- data/lib/configurate/provider/toml.rb +39 -0
- data/lib/configurate/provider/yaml.rb +26 -32
- data/lib/configurate/proxy.rb +42 -13
- data/lib/configurate/setting_path.rb +20 -8
- data/spec/configurate/lookup_chain_spec.rb +11 -9
- data/spec/configurate/provider/dynamic_spec.rb +13 -5
- data/spec/configurate/provider/env_spec.rb +13 -11
- data/spec/configurate/provider/string_hash_spec.rb +82 -0
- data/spec/configurate/provider/toml_spec.rb +112 -0
- data/spec/configurate/provider/yaml_spec.rb +42 -18
- data/spec/configurate/provider_spec.rb +15 -2
- data/spec/configurate/proxy_spec.rb +33 -7
- data/spec/configurate/setting_path_spec.rb +39 -20
- data/spec/configurate_spec.rb +5 -3
- data/spec/spec_helper.rb +8 -5
- metadata +31 -13
- checksums.yaml.gz.asc +0 -11
- data.tar.gz.asc +0 -11
- metadata.gz.asc +0 -11
@@ -1,23 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
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
|
40
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
value =
|
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
|
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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
31
|
+
end
|
data/lib/configurate/proxy.rb
CHANGED
@@ -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
|
-
[
|
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.
|
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
|
-
|
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
|
-
|
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
|
23
|
-
|
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
|
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
|
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
|
-
[
|
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
|
-
[
|
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)}
|
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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
2
4
|
|
3
5
|
class InvalidConfigurationProvider; end
|
4
6
|
class ValidConfigurationProvider
|
5
|
-
def lookup(
|
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
|
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
|
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
|
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
|
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
|
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 {
|
101
|
-
expect(subject.lookup
|
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
|