settingslogic 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,4 +2,4 @@
2
2
  :major: 2
3
3
  :minor: 0
4
4
  :build:
5
- :patch: 5
5
+ :patch: 6
@@ -4,12 +4,22 @@ require "erb"
4
4
  # A simple settings solution using a YAML file. See README for more information.
5
5
  class Settingslogic < Hash
6
6
  class MissingSetting < StandardError; end
7
-
7
+
8
8
  class << self
9
9
  def name # :nodoc:
10
10
  instance.key?("name") ? instance.name : super
11
11
  end
12
-
12
+
13
+ # Enables Settings.get('nested.key.name') for dynamic access
14
+ def get(key)
15
+ parts = key.split('.')
16
+ curs = self
17
+ while p = parts.shift
18
+ curs = curs.send(p)
19
+ end
20
+ curs
21
+ end
22
+
13
23
  def source(value = nil)
14
24
  if value.nil?
15
25
  @source
@@ -27,15 +37,16 @@ class Settingslogic < Hash
27
37
  end
28
38
 
29
39
  def [](key)
30
- # Setting.key.value or Setting[:key][:value] or Setting['key']['value']
31
- fetch(key.to_s,nil)
40
+ instance.fetch(key.to_s, nil)
32
41
  end
33
42
 
34
- def []=(key,val)
35
- # Setting[:key] = 'value' for dynamic settings
36
- store(key.to_s,val)
43
+ def []=(key, val)
44
+ # Setting[:key][:key2] = 'value' for dynamic settings
45
+ val = new(val, source) if val.is_a? Hash
46
+ instance.store(key.to_s, val)
47
+ instance.create_accessor_for(key, val)
37
48
  end
38
-
49
+
39
50
  def load!
40
51
  instance
41
52
  true
@@ -48,12 +59,29 @@ class Settingslogic < Hash
48
59
 
49
60
  private
50
61
  def instance
51
- @instance ||= new
62
+ return @instance if @instance
63
+ @instance = new
64
+ create_accessors!
65
+ @instance
52
66
  end
53
67
 
54
68
  def method_missing(name, *args, &block)
55
69
  instance.send(name, *args, &block)
56
70
  end
71
+
72
+ # It would be great to DRY this up somehow, someday, but it's difficult because
73
+ # of the singleton pattern. Basically this proxies Setting.foo to Setting.instance.foo
74
+ def create_accessors!
75
+ instance.each do |key,val|
76
+ create_accessor_for(key)
77
+ end
78
+ end
79
+
80
+ def create_accessor_for(key)
81
+ return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval
82
+ instance_eval "def #{key}; instance.send(:#{key}); end"
83
+ end
84
+
57
85
  end
58
86
 
59
87
  # Initializes a new settings object. You can initialize an object in any of the following ways:
@@ -67,7 +95,10 @@ class Settingslogic < Hash
67
95
  # if you are using this in rails. If you pass a string it should be an absolute path to your settings file.
68
96
  # Then you can pass a hash, and it just allows you to access the hash via methods.
69
97
  def initialize(hash_or_file = self.class.source, section = nil)
98
+ #puts "new! #{hash_or_file}"
70
99
  case hash_or_file
100
+ when nil
101
+ raise Errno::ENOENT, "No file specified as Settingslogic source"
71
102
  when Hash
72
103
  self.replace hash_or_file
73
104
  else
@@ -75,39 +106,54 @@ class Settingslogic < Hash
75
106
  hash = hash[self.class.namespace] if self.class.namespace
76
107
  self.replace hash
77
108
  end
78
- @section = section || hash_or_file # so end of error says "in application.yml"
109
+ @section = section || self.class.source # so end of error says "in application.yml"
79
110
  create_accessors!
80
111
  end
81
112
 
82
113
  # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used.
83
114
  # Otherwise, create_accessors! (called by new) will have created actual methods for each key.
84
- def method_missing(key, *args, &block)
85
- begin
86
- value = fetch(key.to_s)
87
- rescue IndexError
88
- raise MissingSetting, "Missing setting '#{key}' in #{@section}"
89
- end
115
+ def method_missing(name, *args, &block)
116
+ key = name.to_s
117
+ raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? key
118
+ value = fetch(key)
119
+ create_accessor_for(key)
90
120
  value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
91
121
  end
92
122
 
93
- private
94
- # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set()
95
- # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra
96
- # settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"),
97
- # rather than the app_yml['deploy_to'] hash. Jeezus.
98
- def create_accessors!
99
- self.each do |key,val|
100
- # Use instance_eval/class_eval because they're actually more efficient than define_method{}
101
- # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def
102
- # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/
103
- self.class.class_eval <<-EndEval
104
- def #{key}
105
- return @#{key} if @#{key} # cache (performance)
106
- value = fetch('#{key}')
107
- @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
108
- end
109
- EndEval
110
- end
123
+ def [](key)
124
+ fetch(key.to_s, nil)
125
+ end
126
+
127
+ def []=(key,val)
128
+ # Setting[:key][:key2] = 'value' for dynamic settings
129
+ val = self.class.new(val, @section) if val.is_a? Hash
130
+ store(key.to_s, val)
131
+ create_accessor_for(key, val)
132
+ end
133
+
134
+ # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set()
135
+ # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra
136
+ # settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"),
137
+ # rather than the app_yml['deploy_to'] hash. Jeezus.
138
+ def create_accessors!
139
+ self.each do |key,val|
140
+ create_accessor_for(key)
111
141
  end
142
+ end
112
143
 
144
+ # Use instance_eval/class_eval because they're actually more efficient than define_method{}
145
+ # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def
146
+ # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/
147
+ def create_accessor_for(key, val=nil)
148
+ return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval
149
+ instance_variable_set("@#{key}", val) if val
150
+ self.class.class_eval <<-EndEval
151
+ def #{key}
152
+ return @#{key} if @#{key}
153
+ raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? '#{key}'
154
+ value = fetch('#{key}')
155
+ @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
156
+ end
157
+ EndEval
158
+ end
113
159
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{settingslogic}
8
- s.version = "2.0.5"
8
+ s.version = "2.0.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ben Johnson of Binary Logic"]
12
- s.date = %q{2010-02-01}
12
+ s.date = %q{2010-02-12}
13
13
  s.email = %q{bjohnson@binarylogic.com}
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE",
@@ -1,3 +1,6 @@
1
1
  class Settings < Settingslogic
2
2
  source "#{File.dirname(__FILE__)}/settings.yml"
3
+ end
4
+
5
+ class SettingsInst < Settingslogic
3
6
  end
@@ -16,4 +16,7 @@ language:
16
16
  paradigm: object oriented
17
17
 
18
18
  collides:
19
- does: not
19
+ does: not
20
+ nested:
21
+ collides:
22
+ does: not either
@@ -20,7 +20,7 @@ describe "Settingslogic" do
20
20
  it "should enable erb" do
21
21
  Settings.setting3.should == 25
22
22
  end
23
-
23
+
24
24
  it "should namespace settings" do
25
25
  Settings2.setting1_child.should == "saweet"
26
26
  Settings2.deep.another.should == "my value"
@@ -37,9 +37,13 @@ describe "Settingslogic" do
37
37
  end
38
38
 
39
39
  it "should not collide with global methods" do
40
+ Settings3.nested.collides.does.should == 'not either'
41
+ Settings3[:nested] = 'fooey'
42
+ Settings3[:nested].should == 'fooey'
43
+ Settings3.nested.should == 'fooey'
40
44
  Settings3.collides.does.should == 'not'
41
45
  end
42
-
46
+
43
47
  it "should raise a helpful error message" do
44
48
  e = nil
45
49
  begin
@@ -49,7 +53,7 @@ describe "Settingslogic" do
49
53
  end
50
54
  e.should_not be_nil
51
55
  e.message.should =~ /Missing setting 'missing' in/
52
-
56
+
53
57
  e = nil
54
58
  begin
55
59
  Settings.language.missing
@@ -71,14 +75,62 @@ describe "Settingslogic" do
71
75
  e.message.should =~ /Missing setting 'erlang' in 'language' section/
72
76
 
73
77
  Settings.language['erlang'].should be_nil
74
- Settings.language['erlang'] ||= 5
78
+ Settings.language['erlang'] = 5
75
79
  Settings.language['erlang'].should == 5
76
80
 
77
81
  Settings.language['erlang'] = {'paradigm' => 'functional'}
78
82
  Settings.language.erlang.paradigm.should == 'functional'
83
+ Settings.respond_to?('erlang').should be_false
79
84
 
80
85
  Settings.reload!
81
86
  Settings.language['erlang'].should be_nil
87
+
88
+ Settings.language[:erlang] ||= 5
89
+ Settings.language[:erlang].should == 5
90
+
91
+ Settings.language[:erlang] = {}
92
+ Settings.language[:erlang][:paradigm] = 'functional'
93
+ Settings.language.erlang.paradigm.should == 'functional'
94
+
95
+ Settings[:toplevel] = '42'
96
+ Settings.toplevel.should == '42'
97
+ end
98
+
99
+ it "should raise an error on a nil source argument" do
100
+ class NoSource < Settingslogic; end
101
+ e = nil
102
+ begin
103
+ NoSource.foo.bar
104
+ rescue => e
105
+ e.should be_kind_of Errno::ENOENT
106
+ end
107
+ e.should_not be_nil
108
+ end
109
+
110
+ # This one edge case currently does not pass, because it requires very
111
+ # esoteric code in order to make it pass. It was judged not worth fixing,
112
+ # as it introduces significant complexity for minor gain.
113
+ # it "should handle reloading top-level settings"
114
+ # Settings[:inspect] = 'yeah baby'
115
+ # Settings.inspect.should == 'yeah baby'
116
+ # Settings.reload!
117
+ # Settings.inspect.should == 'Settings'
118
+ # end
119
+
120
+ it "should handle oddly-named settings" do
121
+ Settings.language['some-dash-setting#'] = 'dashtastic'
122
+ Settings.language['some-dash-setting#'].should == 'dashtastic'
123
+ end
124
+
125
+ it "should support instance usage as well" do
126
+ settings = SettingsInst.new(Settings.source)
127
+ settings.setting1.setting1_child.should == "saweet"
128
+ end
129
+
130
+ it "should be able to get() a key with dot.notation" do
131
+ Settings.get('setting1.setting1_child').should == "saweet"
132
+ Settings.get('setting1.deep.another').should == "my value"
133
+ Settings.get('setting1.deep.child.value').should == 2
82
134
  end
83
135
 
84
136
  # Put this test last or else call to .instance will load @instance,
@@ -10,7 +10,7 @@ require 'settings2'
10
10
  require 'settings3'
11
11
 
12
12
  # Needed to test Settings3
13
- def collides
13
+ Object.send :define_method, 'collides' do
14
14
  'collision'
15
15
  end
16
16
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: settingslogic
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Johnson of Binary Logic
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-01 00:00:00 -05:00
12
+ date: 2010-02-12 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15