configliere 0.3.4 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.watchr +20 -0
- data/CHANGELOG.textile +99 -3
- data/Gemfile +26 -0
- data/Gemfile.lock +54 -0
- data/README.textile +162 -138
- data/Rakefile +30 -21
- data/VERSION +1 -1
- data/bin/configliere +77 -77
- data/bin/configliere-decrypt +85 -0
- data/bin/configliere-delete +85 -0
- data/bin/configliere-dump +85 -0
- data/bin/configliere-encrypt +85 -0
- data/bin/configliere-list +85 -0
- data/bin/configliere-set +85 -0
- data/configliere.gemspec +53 -23
- data/examples/config_block_script.rb +9 -2
- data/examples/encrypted_script.rb +28 -16
- data/examples/env_var_script.rb +2 -2
- data/examples/help_message_demo.rb +16 -0
- data/examples/independent_config.rb +28 -0
- data/examples/prompt.rb +23 -0
- data/examples/simple_script.rb +28 -15
- data/examples/simple_script.yaml +1 -1
- data/lib/configliere.rb +22 -24
- data/lib/configliere/commandline.rb +135 -116
- data/lib/configliere/commands.rb +38 -54
- data/lib/configliere/config_block.rb +4 -2
- data/lib/configliere/config_file.rb +30 -52
- data/lib/configliere/crypter.rb +8 -5
- data/lib/configliere/deep_hash.rb +368 -0
- data/lib/configliere/define.rb +83 -89
- data/lib/configliere/encrypted.rb +17 -18
- data/lib/configliere/env_var.rb +5 -7
- data/lib/configliere/param.rb +37 -64
- data/lib/configliere/prompt.rb +23 -0
- data/spec/configliere/commandline_spec.rb +156 -57
- data/spec/configliere/commands_spec.rb +75 -30
- data/spec/configliere/config_block_spec.rb +10 -1
- data/spec/configliere/config_file_spec.rb +83 -55
- data/spec/configliere/crypter_spec.rb +3 -2
- data/spec/configliere/deep_hash_spec.rb +401 -0
- data/spec/configliere/define_spec.rb +121 -42
- data/spec/configliere/encrypted_spec.rb +53 -20
- data/spec/configliere/env_var_spec.rb +24 -4
- data/spec/configliere/param_spec.rb +25 -27
- data/spec/configliere/prompt_spec.rb +50 -0
- data/spec/configliere_spec.rb +3 -9
- data/spec/spec_helper.rb +17 -6
- metadata +110 -35
- data/lib/configliere/core_ext.rb +0 -2
- data/lib/configliere/core_ext/blank.rb +0 -93
- data/lib/configliere/core_ext/hash.rb +0 -108
- data/lib/configliere/core_ext/sash.rb +0 -170
- data/spec/configliere/core_ext/hash_spec.rb +0 -78
- data/spec/configliere/core_ext/sash_spec.rb +0 -312
@@ -15,11 +15,20 @@ describe "Configliere::ConfigBlock" do
|
|
15
15
|
@config.resolve!
|
16
16
|
end
|
17
17
|
it 'resolves blocks last' do
|
18
|
-
Configliere.use :config_block, :
|
18
|
+
Configliere.use :config_block, :encrypted
|
19
19
|
@config.should_receive(:resolve_types!).ordered
|
20
20
|
@config.should_receive(:resolve_finally_blocks!).ordered
|
21
21
|
@config.resolve!
|
22
22
|
end
|
23
|
+
|
24
|
+
describe '#resolve!' do
|
25
|
+
it 'calls super and returns self' do
|
26
|
+
Configliere::ParamParent.class_eval do def resolve!() dummy ; end ; end
|
27
|
+
@config.should_receive(:dummy)
|
28
|
+
@config.resolve!.should equal(@config)
|
29
|
+
Configliere::ParamParent.class_eval do def resolve!() self ; end ; end
|
30
|
+
end
|
31
|
+
end
|
23
32
|
end
|
24
33
|
|
25
34
|
end
|
@@ -1,90 +1,118 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
Configliere.use :config_file
|
3
2
|
|
4
|
-
describe
|
3
|
+
describe Configliere::ConfigFile do
|
5
4
|
before do
|
6
|
-
@config = Configliere.new :my_param => '
|
5
|
+
@config = Configliere::Param.new :my_param => 'default_val', :also_a_param => true
|
7
6
|
FileUtils.stub! :mkdir_p
|
8
7
|
end
|
9
8
|
|
10
|
-
|
9
|
+
it 'is used by default' do
|
10
|
+
@config.should respond_to(:read)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'loading a config file' do
|
11
14
|
before do
|
12
|
-
@
|
13
|
-
@fake_file = '{ :my_param: val }'
|
15
|
+
@fake_file = '{ :my_param: val_from_file }'
|
14
16
|
end
|
17
|
+
|
15
18
|
describe 'successfully' do
|
16
|
-
it '
|
17
|
-
File.should_receive(:open).with(Configliere::DEFAULT_CONFIG_FILE).and_return(':my_settings: ' + @fake_file)
|
18
|
-
@config.read :my_settings
|
19
|
-
end
|
20
|
-
it 'loads an absolute pathname from the given file' do
|
19
|
+
it 'with an absolute pathname uses it directly' do
|
21
20
|
File.should_receive(:open).with('/fake/path.yaml').and_return(@fake_file)
|
22
21
|
@config.read '/fake/path.yaml'
|
23
22
|
end
|
24
|
-
it '
|
23
|
+
it 'with a simple filename, references it to the default config dir' do
|
25
24
|
File.should_receive(:open).with(Configliere::DEFAULT_CONFIG_DIR + '/file.yaml').and_return(@fake_file)
|
26
25
|
@config.read 'file.yaml'
|
27
26
|
end
|
28
|
-
it '
|
29
|
-
File.should_receive(:open).with('/
|
30
|
-
@config.
|
27
|
+
it 'returns the config object for chaining' do
|
28
|
+
File.should_receive(:open).with(Configliere::DEFAULT_CONFIG_DIR + '/file.yaml').and_return(@fake_file)
|
29
|
+
@config.defaults :also_a_param => true
|
30
|
+
@config.read('file.yaml').should == { :my_param => 'val_from_file', :also_a_param => true }
|
31
31
|
end
|
32
32
|
after do
|
33
|
-
@config[:my_param].should == '
|
33
|
+
@config[:my_param].should == 'val_from_file'
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@config.read('nonexistent_file.yaml').should == {}
|
41
|
-
end
|
36
|
+
|
37
|
+
it 'no longer provides a default config file' do
|
38
|
+
lambda{ @config.read(:my_settings) }.should raise_error(Configliere::DeprecatedError)
|
39
|
+
defined?(Configliere::DEFAULT_CONFIG_FILE).should_not be_true
|
42
40
|
end
|
43
|
-
end
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
42
|
+
it 'warns but does not fail if the file is missing' do
|
43
|
+
@config = Configliere::Param.new
|
44
|
+
File.stub(:open).and_raise(Errno::ENOENT)
|
45
|
+
@config.should_receive(:warn).with("Loading empty configliere settings file #{Configliere::DEFAULT_CONFIG_DIR}/nonexistent_file.yaml")
|
46
|
+
@config.read('nonexistent_file.yaml').should == {}
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
describe 'with an environment scope' do
|
51
|
+
before do
|
52
|
+
@config.defaults :reload => :unset
|
53
|
+
@hash_with_subenvs = { :development => { :reload => true }, :production => { :reload => false }}
|
54
|
+
File.stub :open
|
55
|
+
YAML.should_receive(:load).and_return(@hash_with_subenvs)
|
51
56
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
|
58
|
+
it 'slices out a subhash given by :env' do
|
59
|
+
@config.read('f', :env => :development)
|
60
|
+
@config.should == { :reload => true, :my_param => 'default_val', :also_a_param => true }
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'slices out a different subhash with a different :env' do
|
64
|
+
@config.read('f', :env => :production)
|
65
|
+
@config.should == { :reload => false, :my_param => 'default_val', :also_a_param => true }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does no slicing without the :env option' do
|
69
|
+
@config.read('f')
|
70
|
+
@config.should == { :development => { :reload => true }, :production => { :reload => false }, :reload => :unset, :my_param => 'default_val', :also_a_param => true }
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'has no effect if the key given by :env option is absent' do
|
74
|
+
@config.read('f', :env => :foobar)
|
75
|
+
@config.should == { :reload => :unset, :my_param => 'default_val', :also_a_param => true }
|
57
76
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
@config.
|
77
|
+
|
78
|
+
it 'does not type convert the :env option' do
|
79
|
+
@hash_with_subenvs['john_woo'] = { :reload => :sideways }
|
80
|
+
@config.read('f', :env => 'john_woo')
|
81
|
+
@config.should == { :reload => :sideways, :my_param => 'default_val', :also_a_param => true }
|
63
82
|
end
|
64
83
|
end
|
65
84
|
end
|
66
85
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
86
|
+
|
87
|
+
describe 'saves to a config file' do
|
88
|
+
it 'with an absolute pathname, as given' do
|
89
|
+
fake_file = StringIO.new('', 'w')
|
71
90
|
File.should_receive(:open).with('/fake/path.yaml', 'w').and_yield(fake_file)
|
72
|
-
|
73
|
-
|
74
|
-
fake_file.should_receive(:<<).with(mock_dump)
|
75
|
-
Configliere::ConfigFile.merge_into_yaml_file '/fake/path.yaml', :my_settings, :my_param => 'new_val'
|
91
|
+
fake_file.should_receive(:<<).with("--- \n:my_param: default_val\n:also_a_param: true\n")
|
92
|
+
@config.save! '/fake/path.yaml'
|
76
93
|
end
|
77
|
-
end
|
78
94
|
|
79
|
-
|
80
|
-
|
81
|
-
|
95
|
+
it 'with a simple pathname, in the default config dir' do
|
96
|
+
fake_file = StringIO.new('', 'w')
|
97
|
+
File.should_receive(:open).with(Configliere::DEFAULT_CONFIG_DIR + '/file.yaml', 'w').and_yield(fake_file)
|
98
|
+
fake_file.should_receive(:<<).with("--- \n:my_param: default_val\n:also_a_param: true\n")
|
99
|
+
@config.save! 'file.yaml'
|
82
100
|
end
|
83
|
-
|
84
|
-
|
101
|
+
|
102
|
+
it 'and ensures the directory exists' do
|
103
|
+
fake_file = StringIO.new('', 'w')
|
104
|
+
File.stub!(:open).with('/fake/path.yaml', 'w').and_yield(fake_file)
|
105
|
+
FileUtils.should_receive(:mkdir_p).with('/fake')
|
106
|
+
@config.save! '/fake/path.yaml'
|
85
107
|
end
|
86
|
-
|
87
|
-
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#resolve!' do
|
111
|
+
it 'calls super and returns self' do
|
112
|
+
Configliere::ParamParent.class_eval do def resolve!() dummy ; end ; end
|
113
|
+
@config.should_receive(:dummy)
|
114
|
+
@config.resolve!.should equal(@config)
|
115
|
+
Configliere::ParamParent.class_eval do def resolve!() self ; end ; end
|
88
116
|
end
|
89
117
|
end
|
90
118
|
|
@@ -3,12 +3,13 @@ require 'configliere/crypter'
|
|
3
3
|
include Configliere
|
4
4
|
|
5
5
|
describe "Crypter" do
|
6
|
-
ENCRYPTED_FOO_VAL = "
|
6
|
+
ENCRYPTED_FOO_VAL = "cc+Bp5jMUBHFCvPNZIfleeatB4IGaaXjVINl12HOpcs=\n"
|
7
|
+
FOO_VAL_IV = Base64.decode64(ENCRYPTED_FOO_VAL)[0..15]
|
7
8
|
it "encrypts" do
|
8
9
|
# Force the same initialization vector as used to prepare the test value
|
9
10
|
@cipher = Crypter.send(:new_cipher, :encrypt, 'sekrit')
|
10
11
|
Crypter.should_receive(:new_cipher).and_return(@cipher)
|
11
|
-
@cipher.should_receive(:random_iv).and_return
|
12
|
+
@cipher.should_receive(:random_iv).and_return FOO_VAL_IV
|
12
13
|
# OK so do the test now.
|
13
14
|
Crypter.encrypt('foo_val', 'sekrit').should == ENCRYPTED_FOO_VAL
|
14
15
|
end
|
@@ -0,0 +1,401 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
class AwesomeHash < DeepHash ; end
|
4
|
+
|
5
|
+
describe DeepHash do
|
6
|
+
before(:each) do
|
7
|
+
@deep_hash = DeepHash.new.merge!({ :nested_1 => { :nested_2 => { :leaf_3 => "val3" }, :leaf_2 => "val2" }, :leaf_at_top => 'val1b' })
|
8
|
+
@hash = { "str_key" => "strk_val", :sym_key => "symk_val"}
|
9
|
+
@sub = AwesomeHash.new("str_key" => "strk_val", :sym_key => "symk_val")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#initialize" do
|
13
|
+
it 'adopts a Hash when given' do
|
14
|
+
deep_hash = DeepHash.new(@hash)
|
15
|
+
@hash.each{|k,v| deep_hash[k].should == v }
|
16
|
+
deep_hash.keys.any?{|key| key.is_a?(String) }.should be_false
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'converts all pure Hash values into DeepHashes if param is a Hash' do
|
20
|
+
deep_hash = DeepHash.new :sym_key => @hash
|
21
|
+
deep_hash[:sym_key].should be_an_instance_of(DeepHash)
|
22
|
+
# sanity check
|
23
|
+
deep_hash[:sym_key][:sym_key].should == "symk_val"
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not convert Hash subclass values into DeepHashes' do
|
27
|
+
deep_hash = DeepHash.new :sub => @sub
|
28
|
+
deep_hash[:sub].should be_an_instance_of(AwesomeHash)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'converts all value items if value is an Array' do
|
32
|
+
deep_hash = DeepHash.new :arry => { :sym_key => [@hash] }
|
33
|
+
deep_hash[:arry].should be_an_instance_of(DeepHash)
|
34
|
+
# sanity check
|
35
|
+
deep_hash[:arry][:sym_key].first[:sym_key].should == "symk_val"
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'delegates to superclass constructor if param is not a Hash' do
|
39
|
+
deep_hash = DeepHash.new("dash berlin")
|
40
|
+
|
41
|
+
deep_hash["unexisting key"].should == "dash berlin"
|
42
|
+
end
|
43
|
+
end # describe "#initialize"
|
44
|
+
|
45
|
+
describe "#update" do
|
46
|
+
it 'converts all keys into symbols when param is a Hash' do
|
47
|
+
deep_hash = DeepHash.new(@hash)
|
48
|
+
deep_hash.update("starry" => "night")
|
49
|
+
|
50
|
+
deep_hash.keys.any?{|key| key.is_a?(String) }.should be_false
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'converts all Hash values into DeepHashes if param is a Hash' do
|
54
|
+
deep_hash = DeepHash.new :hash => @hash
|
55
|
+
deep_hash.update(:hash => { :sym_key => "is buggy in Ruby 1.8.6" })
|
56
|
+
|
57
|
+
deep_hash[:hash].should be_an_instance_of(DeepHash)
|
58
|
+
end
|
59
|
+
end # describe "#update"
|
60
|
+
|
61
|
+
describe '#[]=' do
|
62
|
+
it 'symbolizes keys' do
|
63
|
+
@deep_hash['leaf_at_top'] = :fedora
|
64
|
+
@deep_hash['new'] = :unseen
|
65
|
+
@deep_hash.should == {:nested_1 => {:nested_2 => {:leaf_3 => "val3"}, :leaf_2 => "val2"}, :leaf_at_top => :fedora, :new => :unseen}
|
66
|
+
end
|
67
|
+
it 'deep-sets dotted vals, replacing values' do
|
68
|
+
@deep_hash['moon.man'] = :cheesy
|
69
|
+
@deep_hash[:moon][:man].should == :cheesy
|
70
|
+
end
|
71
|
+
it 'deep-sets dotted vals, creating new values' do
|
72
|
+
@deep_hash['moon.cheese.type'] = :tilsit
|
73
|
+
@deep_hash[:moon][:cheese][:type].should == :tilsit
|
74
|
+
end
|
75
|
+
it 'deep-sets dotted vals, auto-vivifying intermediate hashes' do
|
76
|
+
@deep_hash['this.that.the_other'] = :fuhgeddaboudit
|
77
|
+
@deep_hash[:this][:that][:the_other].should == :fuhgeddaboudit
|
78
|
+
end
|
79
|
+
it 'converts all Hash value into DeepHash' do
|
80
|
+
deep_hash = DeepHash.new :hash => @hash
|
81
|
+
deep_hash[:hash] = { :sym_key => "is buggy in Ruby 1.8.6" }
|
82
|
+
deep_hash[:hash].should be_an_instance_of(DeepHash)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#[]' do
|
87
|
+
it 'deep-gets dotted vals' do
|
88
|
+
hsh = { :hat => :cat, :basket => :lotion, :moon => { :man => :smiling, :cheese => {:type => :tilsit} } }
|
89
|
+
@deep_hash = Configliere::Param.new hsh.dup
|
90
|
+
@deep_hash['moon.man'].should == :smiling
|
91
|
+
@deep_hash['moon.cheese.type'].should == :tilsit
|
92
|
+
@deep_hash['moon.cheese.smell'].should be_nil
|
93
|
+
@deep_hash['moon.non.existent.interim.values'].should be_nil
|
94
|
+
@deep_hash['moon.non'].should be_nil
|
95
|
+
if (RUBY_VERSION >= '1.9') then lambda{ @deep_hash['hat.cat'] }.should raise_error(TypeError)
|
96
|
+
else lambda{ @deep_hash['hat.cat'] }.should raise_error(NoMethodError, 'undefined method `[]\' for :cat:Symbol') end
|
97
|
+
@deep_hash.should == hsh # shouldn't change from reading (specifically, shouldn't autovivify)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def arrays_should_be_equal arr1, arr2
|
102
|
+
arr1.sort_by{|s| s.to_s }.should == arr2.sort_by{|s| s.to_s }
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#to_hash" do
|
106
|
+
it 'returns instance of Hash' do
|
107
|
+
DeepHash.new(@hash).to_hash.should be_an_instance_of(Hash)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'preserves keys' do
|
111
|
+
deep_hash = DeepHash.new(@hash)
|
112
|
+
converted = deep_hash.to_hash
|
113
|
+
arrays_should_be_equal deep_hash.keys, converted.keys
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'preserves value' do
|
117
|
+
deep_hash = DeepHash.new(@hash)
|
118
|
+
converted = deep_hash.to_hash
|
119
|
+
arrays_should_be_equal deep_hash.values, converted.values
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#compact' do
|
124
|
+
it 'removes nils but not empties or falsehoods' do
|
125
|
+
DeepHash.new({ 1 => nil }).compact.should == {}
|
126
|
+
DeepHash.new({ 1 => nil, 2 => false, 3 => {}, 4 => "", :remains => true }).compact!.should == { 2 => false, 3 => {}, 4 => "", :remains => true }
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'leaves original alone' do
|
130
|
+
deep_hash = DeepHash.new({ 1 => nil, :remains => true })
|
131
|
+
deep_hash.compact.should == { :remains => true }
|
132
|
+
deep_hash.should == { 1 => nil, :remains => true }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '#compact!' do
|
137
|
+
it 'removes nils but not empties or falsehoods' do
|
138
|
+
DeepHash.new({ 1 => nil}).compact!.should == {}
|
139
|
+
DeepHash.new({ 1 => nil, 2 => false, 3 => {}, 4 => "", :remains => true }).compact!.should == { 2 => false, 3 => {}, 4 => "", :remains => true }
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'modifies in-place' do
|
143
|
+
deep_hash = DeepHash.new({ 1 => nil, :remains => true })
|
144
|
+
deep_hash.compact!.should == { :remains => true }
|
145
|
+
deep_hash.should == { :remains => true }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#slice' do
|
150
|
+
before do
|
151
|
+
@deep_hash = DeepHash.new({ :a => 'x', :b => 'y', :c => 10 })
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'returns a new hash with only the given keys' do
|
155
|
+
@deep_hash.slice(:a, :b).should == { :a => 'x', :b => 'y' }
|
156
|
+
@deep_hash.should == { :a => 'x', :b => 'y', :c => 10 }
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'with bang replaces the hash with only the given keys' do
|
160
|
+
@deep_hash.slice!(:a, :b).should == { :c => 10 }
|
161
|
+
@deep_hash.should == { :a => 'x', :b => 'y' }
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'ignores an array key' do
|
165
|
+
@deep_hash.slice([:a, :b], :c).should == { :c => 10 }
|
166
|
+
@deep_hash.should == { :a => 'x', :b => 'y', :c => 10 }
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'with bang ignores an array key' do
|
170
|
+
@deep_hash.slice!([:a, :b], :c).should == { :a => 'x', :b => 'y' }
|
171
|
+
@deep_hash.should == { :c => 10 }
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'uses splatted keys individually' do
|
175
|
+
@deep_hash.slice(*[:a, :b]).should == { :a => 'x', :b => 'y' }
|
176
|
+
@deep_hash.should == { :a => 'x', :b => 'y', :c => 10 }
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'with bank uses splatted keys individually' do
|
180
|
+
@deep_hash.slice!(*[:a, :b]).should == { :c => 10 }
|
181
|
+
@deep_hash.should == { :a => 'x', :b => 'y' }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#extract' do
|
186
|
+
before do
|
187
|
+
@deep_hash = DeepHash.new({ :a => 'x', :b => 'y', :c => 10 })
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'replaces the hash with only the given keys' do
|
191
|
+
@deep_hash.extract!(:a, :b).should == { :a => 'x', :b => 'y' }
|
192
|
+
@deep_hash.should == { :c => 10 }
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'leaves the hash empty if all keys are gone' do
|
196
|
+
@deep_hash.extract!(:a, :b, :c).should == { :a => 'x', :b => 'y', :c => 10 }
|
197
|
+
@deep_hash.should == {}
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'gets values for all given keys even if missing' do
|
201
|
+
@deep_hash.extract!(:bob, :c).should == { :bob => nil, :c => 10 }
|
202
|
+
@deep_hash.should == { :a => 'x', :b => 'y' }
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'is OK when empty' do
|
206
|
+
DeepHash.new.slice!(:a, :b, :c).should == {}
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'returns an instance of the same class' do
|
210
|
+
@deep_hash.slice(:a).should be_a(DeepHash)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'assert_valid_keys' do
|
215
|
+
before do
|
216
|
+
@deep_hash = DeepHash.new({ :failure => "stuff", :funny => "business" })
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'is true and does not raise when valid' do
|
220
|
+
@deep_hash.assert_valid_keys([ :failure, :funny ]).should be_nil
|
221
|
+
@deep_hash.assert_valid_keys(:failure, :funny).should be_nil
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'fails when invalid' do
|
225
|
+
@deep_hash[:failore] = @deep_hash.delete(:failure)
|
226
|
+
lambda{ @deep_hash.assert_valid_keys([ :failure, :funny ]) }.should raise_error(ArgumentError, "Unknown key(s): failore")
|
227
|
+
lambda{ @deep_hash.assert_valid_keys(:failure, :funny) }.should raise_error(ArgumentError, "Unknown key(s): failore")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "#delete" do
|
232
|
+
it 'converts Symbol key into String before deleting' do
|
233
|
+
deep_hash = DeepHash.new(@hash)
|
234
|
+
|
235
|
+
deep_hash.delete(:sym_key)
|
236
|
+
deep_hash.key?("hash").should be_false
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'works with String keys as well' do
|
240
|
+
deep_hash = DeepHash.new(@hash)
|
241
|
+
|
242
|
+
deep_hash.delete("str_key")
|
243
|
+
deep_hash.key?("str_key").should be_false
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
describe "#merge" do
|
248
|
+
before(:each) do
|
249
|
+
@deep_hash = DeepHash.new(@hash).merge(:no => "in between")
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'returns instance of DeepHash' do
|
253
|
+
@deep_hash.should be_an_instance_of(DeepHash)
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'merges in give Hash' do
|
257
|
+
@deep_hash["no"].should == "in between"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe "#fetch" do
|
262
|
+
before(:each) do
|
263
|
+
@deep_hash = DeepHash.new(@hash).merge(:no => "in between")
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'converts key before fetching' do
|
267
|
+
@deep_hash.fetch("no").should == "in between"
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'returns alternative value if key lookup fails' do
|
271
|
+
@deep_hash.fetch("flying", "screwdriver").should == "screwdriver"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
describe "#values_at" do
|
277
|
+
before(:each) do
|
278
|
+
@deep_hash = DeepHash.new(@hash).merge(:no => "in between")
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'is indifferent to whether keys are strings or symbols' do
|
282
|
+
@deep_hash.values_at("sym_key", :str_key, :no).should == ["symk_val", "strk_val", "in between"]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "#symbolize_keys" do
|
287
|
+
it 'with bang returns the deep_hash itself' do
|
288
|
+
deep_hash = DeepHash.new(@hash)
|
289
|
+
deep_hash.symbolize_keys!.object_id.should == deep_hash.object_id
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'returns a dup of itself' do
|
293
|
+
deep_hash = DeepHash.new(@hash)
|
294
|
+
deep_hash.symbolize_keys.should == deep_hash
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe "#reverse_merge" do
|
299
|
+
before do
|
300
|
+
@defaults = { :a => "x", :b => "y", :c => 10 }.freeze
|
301
|
+
@deep_hash = DeepHash.new({ :a => 1, :b => 2 })
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'merges defaults into options, creating a new hash' do
|
305
|
+
@deep_hash.reverse_merge(@defaults).should == { :a => 1, :b => 2, :c => 10 }
|
306
|
+
@deep_hash.should == { :a => 1, :b => 2 }
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'with bang merges! defaults into options, replacing options' do
|
310
|
+
@deep_hash.reverse_merge!(@defaults).should == { :a => 1, :b => 2, :c => 10 }
|
311
|
+
@deep_hash.should == { :a => 1, :b => 2, :c => 10 }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
describe "#deep_merge!" do
|
316
|
+
it "merges two subhashes when they share a key" do
|
317
|
+
@deep_hash.deep_merge!(:nested_1 => { :nested_2 => { :leaf_3_also => "val3a" } })
|
318
|
+
@deep_hash.should == { :nested_1 => { :nested_2 => { :leaf_3_also => "val3a", :leaf_3 => "val3" }, :leaf_2 => "val2" }, :leaf_at_top => 'val1b' }
|
319
|
+
end
|
320
|
+
it "merges two subhashes when they share a symbolized key" do
|
321
|
+
@deep_hash.deep_merge!(:nested_1 => { "nested_2" => { "leaf_3_also" => "val3a" } })
|
322
|
+
@deep_hash.should == { :nested_1 => { :nested_2 => { :leaf_3_also => "val3a", :leaf_3 => "val3" }, :leaf_2 => "val2" }, :leaf_at_top => "val1b" }
|
323
|
+
end
|
324
|
+
it "preserves values in the original" do
|
325
|
+
@deep_hash.deep_merge! :other_key => "other_val"
|
326
|
+
@deep_hash[:nested_1][:leaf_2].should == "val2"
|
327
|
+
@deep_hash[:other_key].should == "other_val"
|
328
|
+
end
|
329
|
+
# it "converts all Hash values into DeepHashes if param is a Hash" do
|
330
|
+
# @deep_hash.deep_merge!({:nested_1 => { :nested_2 => { :leaf_3_also => "val3a" } }, :other1 => { "other2" => "other_val2" }})
|
331
|
+
# @deep_hash[:nested_1].should be_an_instance_of(DeepHash)
|
332
|
+
# @deep_hash[:nested_1][:nested_2].should be_an_instance_of(DeepHash)
|
333
|
+
# @deep_hash[:other1].should be_an_instance_of(DeepHash)
|
334
|
+
# end
|
335
|
+
it "replaces values from the given DeepHash" do
|
336
|
+
@deep_hash.deep_merge!(:nested_1 => { :nested_2 => { :leaf_3 => "new_val3" }, :leaf_2 => { "other2" => "other_val2" }})
|
337
|
+
@deep_hash[:nested_1][:nested_2][:leaf_3].should == 'new_val3'
|
338
|
+
@deep_hash[:nested_1][:leaf_2].should == { :other2 => "other_val2" }
|
339
|
+
end
|
340
|
+
|
341
|
+
it "replaces values from the given DeepHash" do
|
342
|
+
@deep_hash.deep_merge!(:nested_1 => { :nested_2 => { :leaf_3 => [] }, :leaf_2 => nil }, :leaf_at_top => '')
|
343
|
+
@deep_hash[:nested_1][:nested_2][:leaf_3].should == []
|
344
|
+
@deep_hash[:nested_1][:leaf_2].should == "val2"
|
345
|
+
@deep_hash[:leaf_at_top].should == ""
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe "#deep_set" do
|
350
|
+
it 'should set a new value (single arg)' do
|
351
|
+
@deep_hash.deep_set :new_key, 'new_val'
|
352
|
+
@deep_hash[:new_key].should == 'new_val'
|
353
|
+
end
|
354
|
+
it 'should set a new value (multiple args)' do
|
355
|
+
@deep_hash.deep_set :nested_1, :nested_2, :new_key, 'new_val'
|
356
|
+
@deep_hash[:nested_1][:nested_2][:new_key].should == 'new_val'
|
357
|
+
end
|
358
|
+
it 'should replace an existing value (single arg)' do
|
359
|
+
@deep_hash.deep_set :leaf_at_top, 'new_val'
|
360
|
+
@deep_hash[:leaf_at_top].should == 'new_val'
|
361
|
+
end
|
362
|
+
it 'should replace an existing value (multiple args)' do
|
363
|
+
@deep_hash.deep_set :nested_1, :nested_2, 'new_val'
|
364
|
+
@deep_hash[:nested_1][:nested_2].should == 'new_val'
|
365
|
+
end
|
366
|
+
it 'should auto-vivify intermediate hashes' do
|
367
|
+
@deep_hash.deep_set :one, :two, :three, :four, 'new_val'
|
368
|
+
@deep_hash[:one][:two][:three][:four].should == 'new_val'
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
describe "#deep_delete" do
|
373
|
+
it 'should remove the key from the array (multiple args)' do
|
374
|
+
@deep_hash.deep_delete(:nested_1)
|
375
|
+
@deep_hash[:nested_1].should be_nil
|
376
|
+
@deep_hash.should == { :leaf_at_top => 'val1b'}
|
377
|
+
end
|
378
|
+
it 'should remove the key from the array (multiple args)' do
|
379
|
+
@deep_hash.deep_delete(:nested_1, :nested_2, :leaf_3)
|
380
|
+
@deep_hash[:nested_1][:nested_2][:leaf_3].should be_nil
|
381
|
+
@deep_hash.should == {:leaf_at_top => "val1b", :nested_1 => {:leaf_2 => "val2", :nested_2 => {}}}
|
382
|
+
end
|
383
|
+
it 'should return the value if present (single args)' do
|
384
|
+
returned_val = @deep_hash.deep_delete(:leaf_at_top)
|
385
|
+
returned_val.should == 'val1b'
|
386
|
+
end
|
387
|
+
it 'should return the value if present (multiple args)' do
|
388
|
+
returned_val = @deep_hash.deep_delete(:nested_1, :nested_2, :leaf_3)
|
389
|
+
returned_val.should == 'val3'
|
390
|
+
end
|
391
|
+
it 'should return nil if the key is absent (single arg)' do
|
392
|
+
returned_val = @deep_hash.deep_delete(:nested_1, :nested_2, :missing_key)
|
393
|
+
returned_val.should be_nil
|
394
|
+
end
|
395
|
+
it 'should return nil if the key is absent (multiple args)' do
|
396
|
+
returned_val = @deep_hash.deep_delete(:missing_key)
|
397
|
+
returned_val.should be_nil
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|