From d4ee3ec24368136bb0c9b1251f70f5733a31b565 Mon Sep 17 00:00:00 2001 From: Michael Cowgill Date: Mon, 16 Sep 2013 17:24:01 -0700 Subject: [PATCH 1/2] updating to use stacked configuration files - initial commit --- .gitignore | 1 + Gemfile | 2 +- Gemfile.lock | 26 -------------- lib/settingslogic.rb | 73 +++++++++++++++++++++++++++++++------- settingslogic.gemspec | 4 +++ spec/settings2.yml | 31 ++++++++++++++++ spec/settingslogic_spec.rb | 32 ++++++++++++----- spec/spec_helper.rb | 1 + spec/stacked_settings.rb | 8 +++++ 9 files changed, 129 insertions(+), 49 deletions(-) delete mode 100644 Gemfile.lock create mode 100644 spec/settings2.yml create mode 100644 spec/stacked_settings.rb diff --git a/.gitignore b/.gitignore index f7c1204..bcf091d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ benchmarks/* .bundle vendor/bundle .rvmrc +Gemfile.lock diff --git a/Gemfile b/Gemfile index df87cd4..d926697 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,2 @@ -source :rubygems +source 'https://rubygems.org' gemspec \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 6446d66..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,26 +0,0 @@ -PATH - remote: . - specs: - settingslogic (2.0.9) - -GEM - remote: http://rubygems.org/ - specs: - diff-lcs (1.1.3) - rake (10.0.3) - rspec (2.12.0) - rspec-core (~> 2.12.0) - rspec-expectations (~> 2.12.0) - rspec-mocks (~> 2.12.0) - rspec-core (2.12.2) - rspec-expectations (2.12.1) - diff-lcs (~> 1.1.3) - rspec-mocks (2.12.1) - -PLATFORMS - ruby - -DEPENDENCIES - rake - rspec - settingslogic! diff --git a/lib/settingslogic.rb b/lib/settingslogic.rb index a99acaf..581c0ba 100644 --- a/lib/settingslogic.rb +++ b/lib/settingslogic.rb @@ -1,6 +1,7 @@ require "yaml" require "erb" require 'open-uri' +require 'hash_deep_merge' # A simple settings solution using a YAML file. See README for more information. class Settingslogic < Hash @@ -10,7 +11,7 @@ class << self def name # :nodoc: self.superclass != Hash && instance.key?("name") ? instance.name : super end - + # Enables Settings.get('nested.key.name') for dynamic access def get(key) parts = key.split('.') @@ -22,7 +23,50 @@ def get(key) end def source(value = nil) - @source ||= value + if value.is_a?(Array) + @source = flatten_stacked_settings_to_hash(value) + else + @source ||= value + end + end + + # If initialize was given an array of settings, use deep merge to flatten + # all settings into one hash, where the last processed setting is chosen + def flatten_stacked_settings_to_hash (array_of_settings) + resulting_hash = Hash.new + array_of_settings.each do |settings| + new_hash = nil + case settings + + when Hash + new_hash = settings + + when String # assume it's a filename... + begin + file_contents = open(settings).read + new_hash = file_contents.empty? ? {} : YAML.load(ERB.new(file_contents).result).to_hash + rescue + end + + else + new_hash = (settings.to_hash rescue nil) + end + + #continue if empty + next if new_hash.empty? + + if new_hash.is_a?(Hash) + # if self.namespace + # debugger + # new_hash = new_hash[self.namespace] or + # return missing_key("Missing setting '#{self.namespace}' in #{settings.inspect}") + # end + resulting_hash.deep_merge!(new_hash) + else + puts "ExtendedSettings WARN : unable to add settings object : #{ settings.inspect}" + end + end + resulting_hash end def namespace(value = nil) @@ -92,7 +136,6 @@ def create_accessor_for(key) # if you are using this in rails. If you pass a string it should be an absolute path to your settings file. # Then you can pass a hash, and it just allows you to access the hash via methods. def initialize(hash_or_file = self.class.source, section = nil) - #puts "new! #{hash_or_file}" case hash_or_file when nil raise Errno::ENOENT, "No file specified as Settingslogic source" @@ -106,7 +149,12 @@ def initialize(hash_or_file = self.class.source, section = nil) end self.replace hash end - @section = section || self.class.source # so end of error says "in application.yml" + if self.class.source.is_a?(Hash) + puts "Warning can't string interp all hashes, current config is: " + puts self.class.source + section = "Missing setting in custom hash" + end + @section = section || self.class.source # so end of error says "in application.yml" create_accessors! end @@ -167,25 +215,24 @@ def #{key} end EndEval end - + def symbolize_keys - + inject({}) do |memo, tuple| - + k = (tuple.first.to_sym rescue tuple.first) || tuple.first - + v = k.is_a?(Symbol) ? send(k) : tuple.last # make sure the value is accessed the same way Settings.foo.bar works - + memo[k] = v && v.respond_to?(:symbolize_keys) ? v.symbolize_keys : v #recurse for nested hashes - + memo end - + end - + def missing_key(msg) return nil if self.class.suppress_errors - raise MissingSetting, msg end end diff --git a/settingslogic.gemspec b/settingslogic.gemspec index 4b87f4b..c753097 100644 --- a/settingslogic.gemspec +++ b/settingslogic.gemspec @@ -13,6 +13,10 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake' s.add_development_dependency 'rspec' + s.add_development_dependency 'pry' + s.add_development_dependency 'ruby-debug' + + s.add_dependency 'hash-deep-merge', '~> 0.1.1' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") diff --git a/spec/settings2.yml b/spec/settings2.yml new file mode 100644 index 0000000..36397e1 --- /dev/null +++ b/spec/settings2.yml @@ -0,0 +1,31 @@ +setting1: + setting1_child: saweeter + deep: + another: my value + child: + value: 4 + +setting2: 5 +setting3: <%= 5 * 5 %> +name: test is gone + +language: + haskell: + paradigm: functional + smalltalk: + paradigm: object oriented + +collides: + does: not +nested: + collides: + does: not either + +new_stuff: + because: testing has to happen + +array: + - + name: first + - + name: second diff --git a/spec/settingslogic_spec.rb b/spec/settingslogic_spec.rb index 34e4798..09c8263 100644 --- a/spec/settingslogic_spec.rb +++ b/spec/settingslogic_spec.rb @@ -4,11 +4,11 @@ it "should access settings" do Settings.setting2.should == 5 end - + it "should access nested settings" do Settings.setting1.setting1_child.should == "saweet" end - + it "should access settings in nested arrays" do Settings.array.first.name.should == "first" end @@ -39,7 +39,7 @@ Settings.language.haskell.paradigm.should == 'functional' Settings.language.smalltalk.paradigm.should == 'object oriented' end - + it "should not collide with global methods" do Settings3.nested.collides.does.should == 'not either' Settings3[:nested] = 'fooey' @@ -77,7 +77,7 @@ end e.should_not be_nil e.message.should =~ /Missing setting 'erlang' in 'language' section/ - + Settings.language['erlang'].should be_nil Settings.language['erlang'] = 5 Settings.language['erlang'].should == 5 @@ -168,14 +168,14 @@ class NoSource < Settingslogic; end it "should allow a name setting to be overriden" do Settings.name.should == 'test' end - + it "should allow symbolize_keys" do Settings.reload! - result = Settings.language.haskell.symbolize_keys + result = Settings.language.haskell.symbolize_keys result.class.should == Hash - result.should == {:paradigm => "functional"} + result.should == {:paradigm => "functional"} end - + it "should allow symbolize_keys on nested hashes" do Settings.reload! result = Settings.language.symbolize_keys @@ -203,5 +203,19 @@ class NoSource < Settingslogic; end Settings.to_hash.object_id.should_not == Settings.object_id end end - + describe "providing mulitple configuration sources" do + describe "#flatten_stacked_settings_to_hash" do + it "returns a hash from mulitple sources" do + configs = [ + "#{File.dirname(__FILE__)}/settings.yml", + "#{File.dirname(__FILE__)}/settings_empty.yml", + "#{File.dirname(__FILE__)}/settings2.yml"] + config = Settingslogic.new ("#{File.dirname(__FILE__)}/settings.yml") + StackedSettings.flatten_stacked_settings_to_hash(configs).is_a?(Hash).should == true + end + it "returns the last setting for each configuration attributed" do + StackedSettings.setting1.setting1_child.should == 'saweeter' + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cc2cc9a..c002e9b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,7 @@ require 'settings3' require 'settings4' require 'settings_empty' +require 'stacked_settings' # Needed to test Settings3 Object.send :define_method, 'collides' do diff --git a/spec/stacked_settings.rb b/spec/stacked_settings.rb new file mode 100644 index 0000000..b0c1468 --- /dev/null +++ b/spec/stacked_settings.rb @@ -0,0 +1,8 @@ +class StackedSettings < Settingslogic + namespace 'setting1' + # source "#{File.dirname(__FILE__)}/settings.yml" + source ["#{File.dirname(__FILE__)}/settings.yml", + # "#{File.dirname(__FILE__)}/settings_empty.yml", + "#{File.dirname(__FILE__)}/settings2.yml"] + load! +end \ No newline at end of file From 4fa6ea36dd00bcb2de38fd3318d04c33bd1e22ee Mon Sep 17 00:00:00 2001 From: Michael Cowgill Date: Tue, 17 Sep 2013 09:00:59 -0700 Subject: [PATCH 2/2] adding a specs and using to_yaml to improve string parsing for errors --- lib/settingslogic.rb | 12 +----------- spec/settingslogic_spec.rb | 13 +++++++++++-- spec/stacked_settings.rb | 7 +++---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/settingslogic.rb b/lib/settingslogic.rb index 581c0ba..ad81621 100644 --- a/lib/settingslogic.rb +++ b/lib/settingslogic.rb @@ -56,11 +56,6 @@ def flatten_stacked_settings_to_hash (array_of_settings) next if new_hash.empty? if new_hash.is_a?(Hash) - # if self.namespace - # debugger - # new_hash = new_hash[self.namespace] or - # return missing_key("Missing setting '#{self.namespace}' in #{settings.inspect}") - # end resulting_hash.deep_merge!(new_hash) else puts "ExtendedSettings WARN : unable to add settings object : #{ settings.inspect}" @@ -149,12 +144,7 @@ def initialize(hash_or_file = self.class.source, section = nil) end self.replace hash end - if self.class.source.is_a?(Hash) - puts "Warning can't string interp all hashes, current config is: " - puts self.class.source - section = "Missing setting in custom hash" - end - @section = section || self.class.source # so end of error says "in application.yml" + @section = section || self.class.source.to_yaml # so end of error says "in application.yml" create_accessors! end diff --git a/spec/settingslogic_spec.rb b/spec/settingslogic_spec.rb index 09c8263..551a370 100644 --- a/spec/settingslogic_spec.rb +++ b/spec/settingslogic_spec.rb @@ -210,12 +210,21 @@ class NoSource < Settingslogic; end "#{File.dirname(__FILE__)}/settings.yml", "#{File.dirname(__FILE__)}/settings_empty.yml", "#{File.dirname(__FILE__)}/settings2.yml"] - config = Settingslogic.new ("#{File.dirname(__FILE__)}/settings.yml") - StackedSettings.flatten_stacked_settings_to_hash(configs).is_a?(Hash).should == true + StackedSettings.flatten_stacked_settings_to_hash(configs) + .is_a?(Hash).should == true end it "returns the last setting for each configuration attributed" do StackedSettings.setting1.setting1_child.should == 'saweeter' end + it "adds new settings from secondary sources" do + StackedSettings.new_stuff.because.should == "testing has to happen" + end + it "keeps the latest setting for each variable" do + StackedSettings.language.haskell.paradigm.should == 'functional' + end + it "accepts a hash as a source" do + StackedSettings.setting2.should == 10 + end end end end diff --git a/spec/stacked_settings.rb b/spec/stacked_settings.rb index b0c1468..b60d57f 100644 --- a/spec/stacked_settings.rb +++ b/spec/stacked_settings.rb @@ -1,8 +1,7 @@ class StackedSettings < Settingslogic - namespace 'setting1' - # source "#{File.dirname(__FILE__)}/settings.yml" source ["#{File.dirname(__FILE__)}/settings.yml", - # "#{File.dirname(__FILE__)}/settings_empty.yml", - "#{File.dirname(__FILE__)}/settings2.yml"] + "#{File.dirname(__FILE__)}/settings_empty.yml", + "#{File.dirname(__FILE__)}/settings2.yml", + {"setting2" => 10}] load! end \ No newline at end of file