lib/settingslogic.rb in settingslogic-2.0.8 vs lib/settingslogic.rb in settingslogic-2.0.9

- old
+ new

@@ -1,17 +1,18 @@ require "yaml" require "erb" +require 'open-uri' # A simple settings solution using a YAML file. See README for more information. class Settingslogic < Hash class MissingSetting < StandardError; end 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('.') curs = self while p = parts.shift @@ -19,31 +20,19 @@ end curs end def source(value = nil) - if value.nil? - @source - else - @source = value - end + @source ||= value end def namespace(value = nil) - if value.nil? - @namespace - else - @namespace = value - end + @namespace ||= value end def suppress_errors(value = nil) - if value.nil? - @suppress_errors - else - @suppress_errors = value - end + @suppress_errors ||= value end def [](key) instance.fetch(key.to_s, nil) end @@ -57,24 +46,24 @@ def load! instance true end - + def reload! @instance = nil load! end - + private def instance return @instance if @instance @instance = new create_accessors! @instance end - + def method_missing(name, *args, &block) instance.send(name, *args, &block) end # It would be great to DRY this up somehow, someday, but it's difficult because @@ -108,11 +97,12 @@ when nil raise Errno::ENOENT, "No file specified as Settingslogic source" when Hash self.replace hash_or_file else - hash = YAML.load(ERB.new(File.read(hash_or_file)).result).to_hash + file_contents = open(hash_or_file).read + hash = file_contents.empty? ? {} : YAML.load(ERB.new(file_contents).result).to_hash if self.class.namespace hash = hash[self.class.namespace] or return missing_key("Missing setting '#{self.class.namespace}' in #{hash_or_file}") end self.replace hash end @@ -139,10 +129,15 @@ val = self.class.new(val, @section) if val.is_a? Hash store(key.to_s, val) create_accessor_for(key, val) end + # Returns an instance of a Hash object + def to_hash + Hash[self] + end + # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set() # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra # settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"), # rather than the app_yml['deploy_to'] hash. Jeezus. def create_accessors! @@ -154,20 +149,41 @@ # Use instance_eval/class_eval because they're actually more efficient than define_method{} # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/ def create_accessor_for(key, val=nil) return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval - instance_variable_set("@#{key}", val) if val + instance_variable_set("@#{key}", val) self.class.class_eval <<-EndEval def #{key} return @#{key} if @#{key} return missing_key("Missing setting '#{key}' in #{@section}") unless has_key? '#{key}' value = fetch('#{key}') - @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value + @#{key} = if value.is_a?(Hash) + self.class.new(value, "'#{key}' section in #{@section}") + elsif value.is_a?(Array) && value.all?{|v| v.is_a? Hash} + value.map{|v| self.class.new(v)} + else + value + end 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