-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simplify Hashr: * use BasicObject * remove unused features like :_access and :_include * remove raise_missing_keys * rename :define to :default
- Loading branch information
Showing
21 changed files
with
430 additions
and
590 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Gemfile.lock |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,12 @@ Gem::Specification.new do |s| | |
s.name = "hashr" | ||
s.version = Hashr::VERSION | ||
s.authors = ["Sven Fuchs"] | ||
s.email = "[email protected]" | ||
s.email = "[email protected]" | ||
s.homepage = "http://github.com/svenfuchs/hashr" | ||
s.summary = "Simple Hash extension to make working with nested hashes (e.g. for configuration) easier and less error-prone" | ||
s.description = "Simple Hash extension to make working with nested hashes (e.g. for configuration) easier and less error-prone." | ||
s.description = "#{s.summary}." | ||
|
||
s.files = Dir['{lib/**/*,test/**/*,[A-Z]*}'] | ||
s.files = Dir['{lib/**/*,test/**/*,MIT-LICENSE,README.md,Gemfile}'] | ||
s.platform = Gem::Platform::RUBY | ||
s.require_path = 'lib' | ||
s.rubyforge_project = '[none]' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,132 +1,94 @@ | ||
require 'hashr/core_ext/ruby/hash' | ||
|
||
class Hashr < Hash | ||
autoload :EnvDefaults, 'hashr/env_defaults' | ||
|
||
TEMPLATE = new | ||
class Hashr < BasicObject | ||
require 'hashr/core_ext/ruby/hash' | ||
require 'hashr/env_defaults' | ||
|
||
class << self | ||
attr_accessor :raise_missing_keys | ||
attr_reader :defaults | ||
|
||
def define(definition) | ||
@definition = deep_accessorize(definition.deep_symbolize_keys) | ||
def inherited(other) | ||
other.default(defaults) | ||
end | ||
|
||
def definition | ||
@definition ||= {} | ||
def new(*args) | ||
super(self, *args) | ||
end | ||
|
||
def default(defaults) | ||
@defaults = deep_accessorize(defaults) | ||
@defaults = (self.defaults || {}).deep_merge(defaults || {}) | ||
end | ||
alias :define :default | ||
end | ||
|
||
def defaults | ||
@defaults ||= {} | ||
end | ||
attr_reader :class | ||
|
||
def deep_accessorize(hash) | ||
hash.each do |key, value| | ||
next unless value.is_a?(Hash) | ||
value[:_access] ||= [] | ||
value[:_access] = Array(value[:_access]) | ||
value.keys.each { |key| value[:_access] << key if value.respond_to?(key) } | ||
deep_accessorize(value) | ||
end | ||
def initialize(klass, data = nil, defaults = nil, &block) | ||
::Kernel.fail ::ArgumentError.new("Invalid input #{data.inspect}") unless data.nil? || data.is_a?(::Hash) | ||
|
||
data = (data || {}).deep_symbolize_keys | ||
defaults = (defaults || klass.defaults || {}).deep_symbolize_keys | ||
|
||
@class = klass | ||
@data = defaults.deep_merge(data).inject({}) do |result, (key, value)| | ||
result.merge(key => value.is_a?(::Hash) ? ::Hashr.new(value, {}) : value) | ||
end | ||
end | ||
|
||
undef :id if method_defined?(:id) # undefine deprecated method #id on 1.8.x | ||
singleton_class.class_eval(&block) if block_given? | ||
end | ||
|
||
def initialize(data = {}, definition = self.class.definition, &block) | ||
raise(ArgumentError.new("Invalid input #{data.inspect}")) unless data.nil? || data.is_a?(Hash) | ||
replace((deep_hashrize(definition.deep_merge((data || {}).deep_symbolize_keys)))) | ||
deep_defaultize(self) | ||
(class << self; self; end).class_eval(&block) if block_given? | ||
def defined?(key) | ||
@data.key?(key.respond_to?(:to_sym) ? key.to_sym : key) | ||
end | ||
|
||
def [](key, default = nil) | ||
store(key.to_sym, Hashr.new(default)) if default && !key?(key) | ||
super(key.to_sym) | ||
def [](key) | ||
@data[key.to_s] || @data[key.to_sym] | ||
end | ||
|
||
def []=(key, value) | ||
super(key.to_sym, value.is_a?(Hash) ? self.class.new(value, {}) : value) | ||
@data.store(key, value.is_a?(::Hash) ? self.class.new(value, {}) : value) | ||
end | ||
|
||
def set(path, value, stack = []) | ||
tokens = path.to_s.split('.') | ||
tokens.size == 1 ? self[path] = value : self[tokens.shift, Hashr.new].set(tokens.join('.'), value, stack) | ||
def values_at(*keys) | ||
keys.map { |key| self[key] } | ||
end | ||
|
||
def respond_to?(method) | ||
if self.class.raise_missing_keys | ||
key?(method) | ||
else | ||
true | ||
end | ||
true | ||
end | ||
|
||
def method_missing(name, *args, &block) | ||
case name.to_s[-1, 1] | ||
when '?' | ||
!!self[name.to_s[0..-2].to_sym] | ||
!!self[name.to_s[0..-2]] | ||
when '=' | ||
self[name.to_s[0..-2].to_sym] = args.first | ||
self[name.to_s[0..-2]] = args.first | ||
else | ||
raise(IndexError.new("Key #{name.inspect} is not defined.")) if !key?(name) && self.class.raise_missing_keys | ||
self[name] | ||
end | ||
end | ||
|
||
def include_modules(modules) | ||
Array(modules).each { |mod| meta_class.send(:include, mod) } if modules | ||
def to_h | ||
@data.inject({}) do |hash, (key, value)| | ||
hash.merge(key => value.respond_to?(:to_h) ? value.to_h : value) | ||
end | ||
end | ||
alias to_hash to_h | ||
|
||
def include_accessors(accessors) | ||
Array(accessors).each { |accessor| meta_class.send(:define_method, accessor) { self[accessor] } } if accessors | ||
def inspect | ||
"<Hashr #{@data.inspect}>" | ||
end | ||
|
||
def meta_class | ||
class << self; self end | ||
def ==(other) | ||
to_h == other.to_h if other.respond_to?(:to_h) | ||
end | ||
|
||
def to_hash | ||
inject({}) do |hash, (key, value)| | ||
hash[key] = value.is_a?(Hashr) ? value.to_hash : value | ||
hash | ||
end | ||
def instance_of?(const) | ||
self.class == const | ||
end | ||
|
||
protected | ||
|
||
def deep_hashrize(hash) | ||
hash.inject(TEMPLATE.dup) do |result, (key, value)| | ||
case key.to_sym | ||
when :_include | ||
result.include_modules(value) | ||
when :_access | ||
result.include_accessors(value) | ||
else | ||
result.store(key.to_sym, value.is_a?(Hash) ? deep_hashrize(value) : value) | ||
end | ||
result | ||
end | ||
end | ||
|
||
def deep_defaultize(hash) | ||
self.class.defaults.each do |key, value| | ||
case key.to_sym | ||
when :_include | ||
hash.include_modules(value) | ||
when :_access | ||
hash.include_accessors(value) | ||
end | ||
end | ||
|
||
hash.each do |key, value| | ||
deep_defaultize(value) if value.is_a?(Hash) | ||
end | ||
|
||
hash | ||
end | ||
def is_a?(const) | ||
consts = [self.class] | ||
consts << consts.last.superclass while consts.last.superclass | ||
consts.include?(const) | ||
end | ||
alias kind_of? is_a? | ||
end |
Oops, something went wrong.