configliere 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,13 +13,15 @@ module Configliere
13
13
  #
14
14
  def define param, definitions={}
15
15
  self.param_definitions[param].merge! definitions
16
+ self.use(:environment) if definitions.include?(:encrypted)
17
+ self.use(:encrypted) if definitions.include?(:encrypted)
16
18
  self[param] = definitions[:default] if definitions.include?(:default)
17
19
  self.environment_variables definitions[:environment], param if definitions.include?(:environment)
18
20
  end
19
21
 
20
22
  def param_definitions
21
23
  # initialize the param_definitions as an auto-vivifying hash if it's never been set
22
- @param_definitions ||= Hash.new{|hsh, key| hsh[key] = {} }
24
+ @param_definitions ||= Sash.new{|hsh, key| hsh[key] = Sash.new }
23
25
  end
24
26
 
25
27
  # performs type coercion
@@ -153,7 +155,23 @@ module Configliere
153
155
  end
154
156
  hsh
155
157
  end
156
- public
158
+
159
+ # Pretend that any #define'd parameter is a method
160
+ #
161
+ # @example
162
+ # Settings.define :foo
163
+ # Settings.foo = 4
164
+ # Settings.foo #=> 4
165
+ def method_missing meth, *args
166
+ meth.to_s =~ /^(\w+)(=)?$/
167
+ name, setter = [$1, $2]
168
+ super unless name && param_definitions.include?(name)
169
+ if setter && (args.size == 1)
170
+ self[$1] = args.first
171
+ elsif (!setter) && args.empty?
172
+ self[meth]
173
+ else super ; end
174
+ end
157
175
  end
158
176
 
159
177
  Param.class_eval do
@@ -32,7 +32,7 @@ module Configliere
32
32
  def export
33
33
  hsh = super()
34
34
  encrypted_params.each do |param|
35
- val = hsh.deep_delete(*dotted_to_deep_keys(param)) or next
35
+ val = hsh.deep_delete(*convert_key(param)) or next
36
36
  hsh.deep_set( *(dotted_to_encrypted_keys(param) | [encrypted(val)]) )
37
37
  end
38
38
  hsh
@@ -50,7 +50,7 @@ module Configliere
50
50
  # dotted_to_encrypted_keys('amazon.api.key')
51
51
  # #=> [:amazon, :api, :encrypted_key]
52
52
  def dotted_to_encrypted_keys param
53
- encrypted_path = dotted_to_deep_keys(param).dup
53
+ encrypted_path = convert_key(param).dup
54
54
  encrypted_path[-1] = "encrypted_#{encrypted_path.last}".to_sym
55
55
  encrypted_path
56
56
  end
@@ -9,12 +9,12 @@ module Configliere
9
9
  envs.each do |env|
10
10
  case env
11
11
  when Hash
12
- env.each do |env, param|
13
- adopt_environment_variable! env.to_s, param
12
+ env.each do |param, env|
13
+ adopt_environment_variable! param, env
14
14
  end
15
15
  else
16
16
  param = env.to_s.downcase.to_sym
17
- adopt_environment_variable! env.to_s, param
17
+ adopt_environment_variable! param, env
18
18
  end
19
19
  end
20
20
  end
@@ -24,7 +24,8 @@ module Configliere
24
24
  end
25
25
 
26
26
  protected
27
- def adopt_environment_variable! env, param
27
+ def adopt_environment_variable! param, env
28
+ env = env.to_s
28
29
  param_definitions[param][:environment] ||= env
29
30
  val = ENV[env]
30
31
  self[param] = val if val
@@ -1,22 +1,48 @@
1
+ require 'configliere/core_ext/sash.rb'
1
2
  module Configliere
3
+ class ParamParent < ::Hash
4
+ def finally *args, &block
5
+ nil #no-op
6
+ end
7
+ # default export method: self
8
+ def export
9
+ to_hash
10
+ end
11
+ # terminate resolution chain
12
+ def resolve!
13
+ end
14
+
15
+ def validate!
16
+ true
17
+ end
18
+ end
19
+
2
20
  #
3
21
  # Hash of fields to store.
4
22
  #
5
23
  # Any field name beginning with 'decrypted_' automatically creates a
6
24
  # counterpart 'encrypted_' field using the encrypt_pass.
7
25
  #
8
- class Param < ::Hash
26
+ class Param < Configliere::ParamParent
9
27
 
10
- # Initialize with the encrypt_pass and the initial contents of the hash.
28
+ # @param constructor<Object>
29
+ # The default value for the mash. Defaults to an empty hash.
11
30
  #
12
- # @example
13
- # # Create a param for a hypothetical database with encrypt_pass "your_mom"
14
- # Configliere::Param.new 'your_mom',
15
- # :username=>"mysql_username", :decrypted_password=>"mysql_password"
16
- #
17
- def initialize hsh={}
18
- super()
19
- merge! hsh
31
+ # @details [Alternatives]
32
+ # If constructor is a Hash, a new mash will be created based on the keys of
33
+ # the hash and no default value will be set.
34
+ def initialize(constructor = {})
35
+ if constructor.is_a?(Hash)
36
+ super()
37
+ update(constructor) unless constructor.empty?
38
+ else
39
+ super(constructor)
40
+ end
41
+ end
42
+
43
+ # @return [Hash] The mash as a Hash with string keys.
44
+ def to_hash
45
+ Hash.new(default).merge(self)
20
46
  end
21
47
 
22
48
  #
@@ -47,53 +73,50 @@ module Configliere
47
73
 
48
74
  def []= param, val
49
75
  if param =~ /\./
50
- return deep_set( *( dotted_to_deep_keys(param) | [val] ))
76
+ return deep_set( *(convert_key(param) | [val]) )
51
77
  else
52
- super param.to_sym, val
78
+ super param, val
53
79
  end
54
80
  end
55
81
 
56
82
  def [] param
57
83
  if param =~ /\./
58
- return deep_get( *dotted_to_deep_keys(param) )
84
+ return deep_get( *convert_key(param) )
59
85
  else
60
- super param.to_sym
86
+ super param
61
87
  end
62
88
  end
63
89
 
64
90
  def delete param
65
91
  if param =~ /\./
66
- return deep_delete( *dotted_to_deep_keys(param) )
92
+ return deep_delete( *convert_key(param) )
67
93
  else
68
- super param.to_sym
94
+ super param
69
95
  end
70
96
  end
71
97
 
72
- # returns an actual Hash, not a Param < Hash
73
- def to_hash
74
- {}.merge! self
75
- end
76
-
77
98
  def use *args
78
99
  hsh = args.pop if args.last.is_a?(Hash)
79
100
  Configliere.use *args
80
- defaults(hsh) unless hsh.nil?
101
+ self.deep_merge!(hsh) unless hsh.nil?
81
102
  end
82
103
 
104
+ # see Configliere::ConfigBlock#finally
105
+ def finally *args, &block
106
+ use :config_block
107
+ super
108
+ end
83
109
  protected
84
- # turns a dotted param ('moon.cheese.type') into
85
- # an array of sequential keys for deep_set and deep_get
86
- def dotted_to_deep_keys dotted
87
- dotted.to_s.split(".").map{|key| key.to_sym}
110
+ # @param key<Object> The key to convert.
111
+ #
112
+ # @param [Object]
113
+ # The converted key. A dotted param ('moon.cheese.type') becomes
114
+ # an array of sequential keys for deep_set and deep_get
115
+ #
116
+ # @api private
117
+ def convert_key dotted
118
+ dotted.to_s.split(".").map{|key| key.to_sym }
88
119
  end
89
120
 
90
- # simple (no-arg) method_missing callse
91
- def method_missing meth, *args
92
- if args.empty? && meth.to_s =~ /^\w+$/
93
- self[meth]
94
- elsif args.size == 1 && meth.to_s =~ /^(\w+)=$/
95
- self[$1] = args.first
96
- else super(meth, *args) end
97
- end
98
121
  end
99
122
  end
@@ -4,6 +4,7 @@ Configliere.use :config_file
4
4
  describe "Configliere::ConfigFile" do
5
5
  before do
6
6
  @config = Configliere.new :my_param => 'val'
7
+ FileUtils.stub! :mkdir_p
7
8
  end
8
9
 
9
10
  describe 'loads a config file' do
@@ -50,6 +51,12 @@ describe "Configliere::ConfigFile" do
50
51
  fake_file.should_receive(:<<).with("--- \n:my_param: val\n")
51
52
  @config.save! '/fake/path.yaml'
52
53
  end
54
+ it 'ensures the directory exists' do
55
+ fake_file = StringIO.new('', 'w')
56
+ File.stub!(:open).with('/fake/path.yaml', 'w').and_yield(fake_file)
57
+ FileUtils.should_receive(:mkdir_p).with('/fake')
58
+ @config.save! '/fake/path.yaml'
59
+ end
53
60
  end
54
61
  end
55
62
 
@@ -0,0 +1,78 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../spec_helper'))
2
+
3
+ describe Hash do
4
+ before(:each) do
5
+ @hash = { :hsh1a => { :hsh2 => { :key3 => "val3" }, :key2 => "val2" }, :key1b => 'val1b' }
6
+ end
7
+
8
+ describe "#deep_merge!" do
9
+ it "merges two subhashes when they share a key" do
10
+ @hash.deep_merge!(:hsh1a => { :hsh2 => { :key3a => "val3a" } })
11
+ @hash.should == { :hsh1a => { :hsh2 => { :key3a => "val3a", :key3 => "val3" }, :key2 => "val2" }, :key1b => 'val1b' }
12
+ end
13
+ it "preserves values in the original" do
14
+ @hash.deep_merge! :other_key => "other_val"
15
+ @hash[:hsh1a][:key2].should == "val2"
16
+ @hash[:other_key].should == "other_val"
17
+ end
18
+ it "replaces values from the given Hash" do
19
+ @hash.deep_merge!(:hsh1a => { :hsh2 => { :key3 => "new_val3" }, :key2 => { "other2" => "other_val2" }})
20
+ @hash[:hsh1a][:hsh2][:key3].should == 'new_val3'
21
+ @hash[:hsh1a][:key2].should == { "other2" => "other_val2" }
22
+ end
23
+ end
24
+
25
+
26
+ describe "#deep_set" do
27
+ it 'should set a new value (single arg)' do
28
+ @hash.deep_set :new_key, 'new_val'
29
+ @hash[:new_key].should == 'new_val'
30
+ end
31
+ it 'should set a new value (multiple args)' do
32
+ @hash.deep_set :hsh1a, :hsh2, :new_key, 'new_val'
33
+ @hash[:hsh1a][:hsh2][:new_key].should == 'new_val'
34
+ end
35
+ it 'should replace an existing value (single arg)' do
36
+ @hash.deep_set :key1b, 'new_val'
37
+ @hash[:key1b].should == 'new_val'
38
+ end
39
+ it 'should replace an existing value (multiple args)' do
40
+ @hash.deep_set :hsh1a, :hsh2, 'new_val'
41
+ @hash[:hsh1a][:hsh2].should == 'new_val'
42
+ end
43
+ it 'should auto-vivify intermediate hashes' do
44
+ @hash.deep_set :one, :two, :three, :four, 'new_val'
45
+ @hash[:one][:two][:three][:four].should == 'new_val'
46
+ end
47
+ end
48
+
49
+ describe "#deep_delete" do
50
+ it 'should remove the key from the array (multiple args)' do
51
+ @hash.deep_delete(:hsh1a)
52
+ @hash[:hsh1a].should be_nil
53
+ @hash.should == { :key1b => 'val1b'}
54
+ end
55
+ it 'should remove the key from the array (multiple args)' do
56
+ @hash.deep_delete(:hsh1a, :hsh2, :key3)
57
+ @hash[:hsh1a][:hsh2][:key3].should be_nil
58
+ @hash.should == {:key1b=>"val1b", :hsh1a=>{:key2=>"val2", :hsh2=>{}}}
59
+ end
60
+ it 'should return the value if present (single args)' do
61
+ returned_val = @hash.deep_delete(:key1b)
62
+ returned_val.should == 'val1b'
63
+ end
64
+ it 'should return the value if present (multiple args)' do
65
+ returned_val = @hash.deep_delete(:hsh1a, :hsh2, :key3)
66
+ returned_val.should == 'val3'
67
+ end
68
+ it 'should return nil if the key is absent (single arg)' do
69
+ returned_val = @hash.deep_delete(:hsh1a, :hsh2, :missing_key)
70
+ returned_val.should be_nil
71
+ end
72
+ it 'should return nil if the key is absent (multiple args)' do
73
+ returned_val = @hash.deep_delete(:missing_key)
74
+ returned_val.should be_nil
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,313 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../spec_helper'))
2
+
3
+ class AwesomeHash < Hash
4
+ end
5
+
6
+ describe Sash do
7
+ before(:each) do
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
+
13
+ describe "#deep_merge!" do
14
+ before do
15
+ @sash = Sash.new :hsh1 => { :hsh2 => { :key3 => "val3" }, :key2 => "val2" }
16
+ end
17
+ it "merges two subhashes when they share a key" do
18
+ @sash.deep_merge!(:hsh1 => { :hsh2 => { :key3a => "val3a" } })
19
+ @sash.should == { :hsh1 => { :hsh2 => { :key3a => "val3a", :key3 => "val3" }, :key2 => "val2" } }
20
+ end
21
+ it "merges two subhashes when they share a symbolized key" do
22
+ @sash.deep_merge!(:hsh1 => { "hsh2" => { "key3a" => "val3a" } })
23
+ @sash.should == { :hsh1 => { :hsh2 => { :key3a => "val3a", :key3 => "val3" }, :key2 => "val2" } }
24
+ end
25
+ it "preserves values in the original" do
26
+ @sash.deep_merge! :other_key => "other_val"
27
+ @sash[:other_key].should == "other_val"
28
+ @sash[:hsh1][:key2].should == "val2"
29
+ end
30
+
31
+ it "converts all keys into symbols when param is a Hash" do
32
+ @sash.deep_merge!(:hsh1 => { "hsh2" => { "key3a" => "val3a" } })
33
+ @sash.should == { :hsh1 => { :hsh2 => { :key3a => "val3a", :key3 => "val3" }, :key2 => "val2" } }
34
+ end
35
+ it "converts all Hash values into Sashes if param is a Hash" do
36
+ @sash.deep_merge!({:hsh1 => { :hsh2 => { :key3a => "val3a" } }, :other1 => { "other2" => "other_val2" }})
37
+ @sash[:hsh1].should be_an_instance_of(Sash)
38
+ @sash[:hsh1][:hsh2].should be_an_instance_of(Sash)
39
+ @sash[:other1].should be_an_instance_of(Sash)
40
+ end
41
+ it "replaces values from the given Hash" do
42
+ @sash.deep_merge!(:hsh1 => { :hsh2 => { :key3 => "new_val3" }, :key2 => { "other2" => "other_val2" }})
43
+ @sash[:hsh1][:hsh2][:key3].should == 'new_val3'
44
+ @sash[:hsh1][:key2].should == { :other2 => "other_val2" }
45
+ end
46
+ end
47
+
48
+ describe "#initialize" do
49
+ it 'converts all keys into symbols when param is a Hash' do
50
+ sash = Sash.new(@hash)
51
+ sash.keys.any? { |key| key.is_a?(String) }.should be_false
52
+ end
53
+
54
+ it 'converts all pure Hash values into Sashes if param is a Hash' do
55
+ sash = Sash.new :sym_key => @hash
56
+
57
+ sash[:sym_key].should be_an_instance_of(Sash)
58
+ # sanity check
59
+ sash[:sym_key][:sym_key].should == "symk_val"
60
+ end
61
+
62
+ it 'doesn not convert Hash subclass values into Sashes' do
63
+ sash = Sash.new :sub => @sub
64
+ sash[:sub].should be_an_instance_of(AwesomeHash)
65
+ end
66
+
67
+ it 'converts all value items if value is an Array' do
68
+ sash = Sash.new :arry => { :sym_key => [@hash] }
69
+
70
+ sash[:arry].should be_an_instance_of(Sash)
71
+ # sanity check
72
+ sash[:arry][:sym_key].first[:sym_key].should == "symk_val"
73
+
74
+ end
75
+
76
+ it 'delegates to superclass constructor if param is not a Hash' do
77
+ sash = Sash.new("dash berlin")
78
+
79
+ sash["unexisting key"].should == "dash berlin"
80
+ end
81
+ end # describe "#initialize"
82
+
83
+
84
+
85
+ describe "#update" do
86
+ it 'converts all keys into symbols when param is a Hash' do
87
+ sash = Sash.new(@hash)
88
+ sash.update("starry" => "night")
89
+
90
+ sash.keys.any?{|key| key.is_a?(String) }.should be_false
91
+ end
92
+
93
+ it 'converts all Hash values into Sashes if param is a Hash' do
94
+ sash = Sash.new :hash => @hash
95
+ sash.update(:hash => { :sym_key => "is buggy in Ruby 1.8.6" })
96
+
97
+ sash[:hash].should be_an_instance_of(Sash)
98
+ end
99
+ end # describe "#update"
100
+
101
+
102
+
103
+ describe "#[]=" do
104
+ it 'converts key into symbol' do
105
+ sash = Sash.new(@hash)
106
+ sash["str_key"] = { "starry" => "night" }
107
+
108
+ sash.keys.any?{|key| key.is_a?(String) }.should be_false
109
+ end
110
+
111
+ it 'converts all Hash value into Sash' do
112
+ sash = Sash.new :hash => @hash
113
+ sash[:hash] = { :sym_key => "is buggy in Ruby 1.8.6" }
114
+
115
+ sash[:hash].should be_an_instance_of(Sash)
116
+ end
117
+ end # describe "#[]="
118
+
119
+
120
+
121
+ describe "#key?" do
122
+ before(:each) do
123
+ @sash = Sash.new(@hash)
124
+ end
125
+
126
+ it 'converts key before lookup' do
127
+ @sash.key?("str_key").should be_true
128
+ @sash.key?(:str_key).should be_true
129
+
130
+ @sash.key?("sym_key").should be_true
131
+ @sash.key?(:sym_key).should be_true
132
+
133
+ @sash.key?(:rainclouds).should be_false
134
+ @sash.key?("rainclouds").should be_false
135
+ end
136
+
137
+ it 'is aliased as include?' do
138
+ @sash.include?("str_key").should be_true
139
+ @sash.include?(:str_key).should be_true
140
+
141
+ @sash.include?("sym_key").should be_true
142
+ @sash.include?(:sym_key).should be_true
143
+
144
+ @sash.include?(:rainclouds).should be_false
145
+ @sash.include?("rainclouds").should be_false
146
+ end
147
+
148
+ it 'is aliased as member?' do
149
+ @sash.member?("str_key").should be_true
150
+ @sash.member?(:str_key).should be_true
151
+
152
+ @sash.member?("sym_key").should be_true
153
+ @sash.member?(:sym_key).should be_true
154
+
155
+ @sash.member?(:rainclouds).should be_false
156
+ @sash.member?("rainclouds").should be_false
157
+ end
158
+ end # describe "#key?"
159
+
160
+ def arrays_should_be_equal arr1, arr2
161
+ arr1.sort_by{|s| s.to_s }.should == arr2.sort_by{|s| s.to_s }
162
+ end
163
+
164
+ describe "#dup" do
165
+ it 'returns instance of Sash' do
166
+ Sash.new(@hash).dup.should be_an_instance_of(Sash)
167
+ end
168
+
169
+ it 'preserves keys' do
170
+ sash = Sash.new(@hash)
171
+ dup = sash.dup
172
+
173
+ arrays_should_be_equal sash.keys, dup.keys
174
+ end
175
+
176
+ it 'preserves value' do
177
+ sash = Sash.new(@hash)
178
+ dup = sash.dup
179
+
180
+ arrays_should_be_equal sash.values, dup.values
181
+ end
182
+ end
183
+
184
+
185
+
186
+ describe "#to_hash" do
187
+ it 'returns instance of Hash' do
188
+ Sash.new(@hash).to_hash.should be_an_instance_of(Hash)
189
+ end
190
+
191
+ it 'preserves keys' do
192
+ sash = Sash.new(@hash)
193
+ converted = sash.to_hash
194
+ arrays_should_be_equal sash.keys, converted.keys
195
+ end
196
+
197
+ it 'preserves value' do
198
+ sash = Sash.new(@hash)
199
+ converted = sash.to_hash
200
+ arrays_should_be_equal sash.values, converted.values
201
+ end
202
+ end
203
+
204
+
205
+
206
+ describe "#stringify_keys" do
207
+ it 'returns instance of Sash' do
208
+ Sash.new(@hash).stringify_keys.should be_an_instance_of(Hash)
209
+ end
210
+
211
+ it 'converts keys to symbols' do
212
+ sash = Sash.new(@hash)
213
+ converted = sash.stringify_keys
214
+
215
+ converted_keys = converted.keys.sort{|k1, k2| k1.to_s <=> k2.to_s}
216
+ orig_keys = sash.keys.map{|k| k.to_sym}.sort{|i1, i2| i1.to_s <=> i2.to_s}
217
+
218
+ converted_keys.should == orig_keys
219
+ end
220
+
221
+ it 'preserves value' do
222
+ sash = Sash.new(@hash)
223
+ converted = sash.stringify_keys
224
+
225
+ arrays_should_be_equal sash.values, converted.values
226
+ end
227
+ end
228
+
229
+
230
+
231
+ describe "#delete" do
232
+ it 'converts Symbol key into String before deleting' do
233
+ sash = Sash.new(@hash)
234
+
235
+ sash.delete(:sym_key)
236
+ sash.key?("hash").should be_false
237
+ end
238
+
239
+ it 'works with String keys as well' do
240
+ sash = Sash.new(@hash)
241
+
242
+ sash.delete("str_key")
243
+ sash.key?("str_key").should be_false
244
+ end
245
+ end
246
+
247
+
248
+
249
+ describe "#merge" do
250
+ before(:each) do
251
+ @sash = Sash.new(@hash).merge(:no => "in between")
252
+ end
253
+
254
+ it 'returns instance of Sash' do
255
+ @sash.should be_an_instance_of(Sash)
256
+ end
257
+
258
+ it 'merges in give Hash' do
259
+ @sash["no"].should == "in between"
260
+ end
261
+ end
262
+
263
+
264
+
265
+ describe "#fetch" do
266
+ before(:each) do
267
+ @sash = Sash.new(@hash).merge(:no => "in between")
268
+ end
269
+
270
+ it 'converts key before fetching' do
271
+ @sash.fetch("no").should == "in between"
272
+ end
273
+
274
+ it 'returns alternative value if key lookup fails' do
275
+ @sash.fetch("flying", "screwdriver").should == "screwdriver"
276
+ end
277
+ end
278
+
279
+
280
+ describe "#default" do
281
+ before(:each) do
282
+ @sash = Sash.new(:yet_another_technical_revolution)
283
+ end
284
+
285
+ it 'returns default value unless key exists in sash' do
286
+ @sash.default("peak oil is now behind, baby").should == :yet_another_technical_revolution
287
+ end
288
+
289
+ it 'returns existing value if key is String and exists in sash' do
290
+ @sash.update("no" => "in between")
291
+ @sash.default("no").should == "in between"
292
+ end
293
+ end
294
+
295
+
296
+ describe "#values_at" do
297
+ before(:each) do
298
+ @sash = Sash.new(@hash).merge(:no => "in between")
299
+ end
300
+
301
+ it 'is indifferent to whether keys are strings or symbols' do
302
+ @sash.values_at("sym_key", :str_key, :no).should == ["symk_val", "strk_val", "in between"]
303
+ end
304
+ end
305
+
306
+
307
+ describe "#symbolize_keys!" do
308
+ it 'returns the sash itself' do
309
+ sash = Sash.new(@hash)
310
+ sash.symbolize_keys!.object_id.should == sash.object_id
311
+ end
312
+ end
313
+ end