settingslogic 2.0.5 → 2.0.6

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