configliere 0.0.2 → 0.0.3

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.
@@ -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