hashr 1.0.0 → 2.0.0.rc1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 363367721272caa3b82603de2d83346a509a4a5f
4
- data.tar.gz: 5a3b97d5394ae7c46e6f926e8e72cd5f37b3856b
3
+ metadata.gz: 865d5c744eb989eb6faa51b252a52b83ac31fd49
4
+ data.tar.gz: a69e87efba10818c1af7036e02d75327210dbc3b
5
5
  SHA512:
6
- metadata.gz: 8a89f6e9a054164233456eabe2a4e14ce9ef42f4d6c80a2ebb3c42e185412ab39ba56dda24079293593238c089762444583cdfaaaaa955c5a33442abc7991eda
7
- data.tar.gz: 609ed7d5b46af9cf45c52783efd0f97420280eb42dfb2b9c204805e9dd4a5036163244c1aebab428065e5a5d12b2f711491d45b0af7ae9d4dc8621933239b7fd
6
+ metadata.gz: 1e2285af349146a905c6bcc53f5bdacc9d644f2ca48e05e69d700e18082d2285dd1b49e16670808a470e72e6ced4795ceb614d6d4eeded4310e61c9f1ffb559e
7
+ data.tar.gz: 993a3e986b50d2c9c34bd2ce153f2a25c10d0530962cca561880361f9080d83cdbb87f83de34b3dc11029ed88a5cf479d9de259396dc7d09b089f00d9820a336
data/Gemfile CHANGED
@@ -1,2 +1,7 @@
1
1
  source 'https://rubygems.org'
2
+
2
3
  gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ end
data/README.md CHANGED
@@ -2,145 +2,107 @@
2
2
 
3
3
  # Hashr
4
4
 
5
- Hashr is a very simple and tiny class derived from Ruby's core Hash class which makes using nested hashes for configuration (and other purposes) easier and less repetive and error prone.
5
+ Hashr is a very simple and tiny class which makes using nested hashes for
6
+ configuration (and other purposes) easier.
6
7
 
7
8
  It supports the following features:
8
9
 
9
10
  * method read and write access
10
11
  * automatic predicate (boolean, i.e. `?`) methods
11
12
  * easy defaults
12
- * easy inclusion of modules into nested hashes
13
- * automatic symbolized keys
13
+ * indifferent (strings vs symbols) keys
14
14
 
15
15
  ## Usage
16
16
 
17
17
  Directly use Hashr instances like this:
18
18
 
19
- config = Hashr.new('foo' => { 'bar' => 'bar' })
19
+ ```ruby
20
+ config = Hashr.new(foo: { bar: 'bar' })
20
21
 
21
- config.foo? # => true
22
- config.foo # => { :bar => 'bar' }
22
+ config.foo? # => true
23
+ config.foo # => { bar: 'bar' }
23
24
 
24
- config.foo.bar? # => true
25
- config.foo.bar # => 'bar'
25
+ config.foo.bar? # => true
26
+ config.foo.bar # => 'bar'
26
27
 
27
- config.foo.bar = 'bar!'
28
- config.foo.bar # => 'bar!'
28
+ config.foo.bar = 'bar'
29
+ config.foo.bar # => 'bar'
29
30
 
30
- config.foo.baz = 'baz'
31
- config.foo.baz # => 'baz'
31
+ config.foo.baz = 'baz'
32
+ config.foo.baz # => 'baz'
33
+ ```
32
34
 
33
- Be aware that by default missing keys won't raise an exception but instead behave like Hash access:
35
+ Hash core methods are not available but assume you mean to look up keys with
36
+ the same name:
34
37
 
35
- config = Hashr.new
36
- config.foo? # => false
37
- config.foo # => nil
38
+ ```ruby
39
+ config = Hashr.new(count: 1, key: 'key')
40
+ config.count # => 1
41
+ config.key # => 'key'
42
+ ```
38
43
 
39
- You can make Hashr raise an `IndexError` though like this:
44
+ In order to check a hash stored on a certain key you can convert it to a Ruby
45
+ Hash:
40
46
 
41
- Hashr.raise_missing_keys = true
42
- config = Hashr.new
43
- config.foo? # => false
44
- config.foo # => raises an IndexError "Key :foo is not defined."
47
+ ```ruby
48
+ config = Hashr.new(count: 1, key: 'key')
49
+ config.to_h.count # => 2
50
+ config.to_h.key # => raises ArgumentError: "wrong number of arguments (0 for 1)"
51
+ ```
45
52
 
46
- You can also anonymously overwrite core Hash methods like this:
53
+ Missing keys won't raise an exception but instead behave like Hash access:
47
54
 
48
- config = Hashr.new(:count => 3) do
49
- def count
50
- self[:count]
51
- end
52
- end
53
- config.count # => 3
55
+ ```ruby
56
+ config = Hashr.new
57
+ config.foo? # => false
58
+ config.foo # => nil
59
+ ```
54
60
 
55
- And you can anonymously provide defaults like this:
61
+ ## Defaults
56
62
 
57
- data = { :foo => 'foo' }
58
- defaults = { :bar => 'bar' }
59
- config = Hashr.new(data, defaults)
60
- config.foo # => 'foo'
61
- config.bar # => 'bar'
63
+ Defaults can be defined per class:
62
64
 
63
- But you can obvioulsy also derive a custom class to define defaults and overwrite core Hash methods like this:
65
+ ```ruby
66
+ class Config < Hashr
67
+ default boxes: { memory: '1024' }
68
+ end
64
69
 
65
- class Config < Hashr
66
- define :foo => { :bar => 'bar' }
70
+ config = Config.new
71
+ config.boxes.memory # => 1024
72
+ ```
67
73
 
68
- def count
69
- self[:count]
70
- end
71
- end
74
+ Or passed to the instance:
72
75
 
73
- config = Config.new
74
- config.foo.bar # => 'bar'
76
+ ```ruby
77
+ data = {}
78
+ defaults = { boxes: { memory: '1024' } }
75
79
 
76
- Include modules to nested hashes like this:
77
-
78
- class Config < Hashr
79
- module Boxes
80
- def count
81
- self[:count] # overwrites a Hash method to return the Hash's content here
82
- end
83
-
84
- def names
85
- @names ||= (1..count).map { |num| "box-#{num}" }
86
- end
87
- end
88
-
89
- define :boxes => { :count => 3, :_include => Boxes }
90
- end
91
-
92
- config = Config.new
93
- config.boxes # => { :count => 3 }
94
- config.boxes.count # => 3
95
- config.boxes.names # => ["box-1", "box-2", "box-3"]
96
-
97
- As overwriting Hash methods for method access to keys is a common pattern there's a short cut to it:
98
-
99
- class Config < Hashr
100
- define :_access => [:count, :key]
101
- end
102
-
103
- config = Config.new(:count => 3, :key => 'key')
104
- config.count # => 3
105
- config.key # => 'key'
106
-
107
- Both `:_include` and `:_access` can be defined as defaults, i.e. so that they will be used on all nested hashes:
108
-
109
- class Config < Hashr
110
- default :_access => :key
111
- end
112
-
113
- config = Config.new(:key => 'key', :foo => { :key => 'foo.key' })
114
- config.key # => 'key'
115
- config.foo.key # => 'foo.key'
80
+ config = Hashr.new(data, defaults)
81
+ config.boxes.memory # => 1024
82
+ ```
116
83
 
117
84
  ## Environment defaults
118
85
 
119
- Hashr includes a simple module that makes it easy to overwrite configuration defaults from environment variables:
86
+ Hashr includes a simple module that makes it easy to overwrite configuration
87
+ defaults from environment variables:
120
88
 
121
- class Config < Hashr
122
- extend Hashr::EnvDefaults
89
+ ```ruby
90
+ class Config < Hashr
91
+ extend Hashr::Env
123
92
 
124
- self.env_namespace = 'foo'
93
+ self.env_namespace = 'foo'
125
94
 
126
- define :boxes => { :memory => '1024' }
127
- end
95
+ default boxes: { memory: '1024' }
96
+ end
97
+ ```
128
98
 
129
99
  Now when an environment variable is defined then it will overwrite the default:
130
100
 
131
- ENV['FOO_BOXES_MEMORY'] = '2048'
132
- config = Config.new
133
- config.boxes.memory # => '2048'
134
-
135
- ## Running the tests
136
-
137
- You can run the tests as follows:
138
-
139
- # going through bundler
140
- bundle exec rake
141
-
142
- # using just ruby
143
- ruby -rubygems -Ilib:test test/hashr_test.rb
101
+ ```ruby
102
+ ENV['FOO_BOXES_MEMORY'] = '2048'
103
+ config = Config.new
104
+ config.boxes.memory # => '2048'
105
+ ```
144
106
 
145
107
  ## Other libraries
146
108
 
@@ -1,132 +1,108 @@
1
1
  require 'hashr/core_ext/ruby/hash'
2
2
 
3
- class Hashr < Hash
4
- autoload :EnvDefaults, 'hashr/env_defaults'
5
-
6
- TEMPLATE = new
3
+ class Hashr < BasicObject
4
+ require 'hashr/delegate/conditional'
5
+ require 'hashr/env'
7
6
 
8
7
  class << self
9
- attr_accessor :raise_missing_keys
8
+ attr_reader :defaults
10
9
 
11
- def define(definition)
12
- @definition = deep_accessorize(definition.deep_symbolize_keys)
10
+ def inherited(other)
11
+ other.default(defaults)
13
12
  end
14
13
 
15
- def definition
16
- @definition ||= {}
14
+ def new(*args)
15
+ super(self, *args)
17
16
  end
18
17
 
19
18
  def default(defaults)
20
- @defaults = deep_accessorize(defaults)
19
+ @defaults = (self.defaults || {}).deep_merge(defaults || {})
21
20
  end
21
+ alias :define :default
22
22
 
23
- def defaults
24
- @defaults ||= {}
23
+ def const_missing(name)
24
+ Kernel.const_get(name)
25
25
  end
26
+ end
27
+
28
+ attr_reader :class
29
+
30
+ def initialize(klass, data = nil, defaults = nil, &block)
31
+ ::Kernel.fail ::ArgumentError.new("Invalid input #{data.inspect}") unless data.nil? || data.is_a?(::Hash)
26
32
 
27
- def deep_accessorize(hash)
28
- hash.each do |key, value|
29
- next unless value.is_a?(Hash)
30
- value[:_access] ||= []
31
- value[:_access] = Array(value[:_access])
32
- value.keys.each { |key| value[:_access] << key if value.respond_to?(key) }
33
- deep_accessorize(value)
34
- end
33
+ data = (data || {}).deep_symbolize_keys
34
+ defaults = (defaults || klass.defaults || {}).deep_symbolize_keys
35
+
36
+ @class = klass
37
+ @data = defaults.deep_merge(data).inject({}) do |result, (key, value)|
38
+ result.merge(key => value.is_a?(::Hash) ? ::Hashr.new(value, {}) : value)
35
39
  end
36
40
  end
37
41
 
38
- undef :id if method_defined?(:id) # undefine deprecated method #id on 1.8.x
39
-
40
- def initialize(data = {}, definition = self.class.definition, &block)
41
- raise(ArgumentError.new("Invalid input #{data.inspect}")) unless data.nil? || data.is_a?(Hash)
42
- replace((deep_hashrize(definition.deep_merge((data || {}).deep_symbolize_keys))))
43
- deep_defaultize(self)
44
- (class << self; self; end).class_eval(&block) if block_given?
42
+ def defined?(key)
43
+ @data.key?(to_key(key))
45
44
  end
46
45
 
47
- def [](key, default = nil)
48
- store(key.to_sym, Hashr.new(default)) if default && !key?(key)
49
- super(key.to_sym)
46
+ def [](key)
47
+ @data[to_key(key)]
50
48
  end
51
49
 
52
50
  def []=(key, value)
53
- super(key.to_sym, value.is_a?(Hash) ? self.class.new(value, {}) : value)
51
+ @data.store(to_key(key), value.is_a?(::Hash) ? ::Hashr.new(value, {}) : value)
54
52
  end
55
53
 
56
- def set(path, value, stack = [])
57
- tokens = path.to_s.split('.')
58
- tokens.size == 1 ? self[path] = value : self[tokens.shift, Hashr.new].set(tokens.join('.'), value, stack)
54
+ def values_at(*keys)
55
+ keys.map { |key| self[key] }
59
56
  end
60
57
 
61
- def respond_to?(method)
62
- if self.class.raise_missing_keys
63
- key?(method)
64
- else
65
- true
66
- end
58
+ def respond_to?(*args)
59
+ true
67
60
  end
68
61
 
69
62
  def method_missing(name, *args, &block)
70
63
  case name.to_s[-1, 1]
71
64
  when '?'
72
- !!self[name.to_s[0..-2].to_sym]
65
+ !!self[name.to_s[0..-2]]
73
66
  when '='
74
- self[name.to_s[0..-2].to_sym] = args.first
67
+ self[name.to_s[0..-2]] = args.first
75
68
  else
76
- raise(IndexError.new("Key #{name.inspect} is not defined.")) if !key?(name) && self.class.raise_missing_keys
77
69
  self[name]
78
70
  end
79
71
  end
80
72
 
81
- def include_modules(modules)
82
- Array(modules).each { |mod| meta_class.send(:include, mod) } if modules
73
+ def try(key)
74
+ defined?(key) ? self[key] : nil # TODO needs to look for to_h etc
83
75
  end
84
76
 
85
- def include_accessors(accessors)
86
- Array(accessors).each { |accessor| meta_class.send(:define_method, accessor) { self[accessor] } } if accessors
77
+ def to_h
78
+ @data.inject({}) do |hash, (key, value)|
79
+ hash.merge(key => value.respond_to?(:to_h) ? value.to_h : value)
80
+ end
87
81
  end
82
+ alias to_hash to_h
88
83
 
89
- def meta_class
90
- class << self; self end
84
+ def ==(other)
85
+ to_h == other.to_h if other.respond_to?(:to_h)
91
86
  end
92
87
 
93
- def to_hash
94
- inject({}) do |hash, (key, value)|
95
- hash[key] = value.is_a?(Hashr) ? value.to_hash : value
96
- hash
97
- end
88
+ def instance_of?(const)
89
+ self.class == const
98
90
  end
99
91
 
100
- protected
101
-
102
- def deep_hashrize(hash)
103
- hash.inject(TEMPLATE.dup) do |result, (key, value)|
104
- case key.to_sym
105
- when :_include
106
- result.include_modules(value)
107
- when :_access
108
- result.include_accessors(value)
109
- else
110
- result.store(key.to_sym, value.is_a?(Hash) ? deep_hashrize(value) : value)
111
- end
112
- result
113
- end
114
- end
92
+ def is_a?(const)
93
+ consts = [self.class]
94
+ consts << consts.last.superclass while consts.last.superclass
95
+ consts.include?(const)
96
+ end
97
+ alias kind_of? is_a?
98
+
99
+ def inspect
100
+ "<#{self.class.name} #{@data.inspect}>"
101
+ end
102
+
103
+ private
115
104
 
116
- def deep_defaultize(hash)
117
- self.class.defaults.each do |key, value|
118
- case key.to_sym
119
- when :_include
120
- hash.include_modules(value)
121
- when :_access
122
- hash.include_accessors(value)
123
- end
124
- end
125
-
126
- hash.each do |key, value|
127
- deep_defaultize(value) if value.is_a?(Hash)
128
- end
129
-
130
- hash
105
+ def to_key(key)
106
+ key.respond_to?(:to_sym) ? key.to_sym : key
131
107
  end
132
108
  end
@@ -1,14 +1,4 @@
1
1
  class Hash
2
- def slice(*keep_keys)
3
- h = {}
4
- keep_keys.each { |key| h[key] = fetch(key) if key?(key) }
5
- h
6
- end unless Hash.method_defined?(:slice)
7
-
8
- def except(*less_keys)
9
- slice(*keys - less_keys)
10
- end unless Hash.method_defined?(:except)
11
-
12
2
  def deep_symbolize_keys
13
3
  symbolizer = lambda do |value|
14
4
  case value
@@ -24,15 +14,11 @@ class Hash
24
14
  result[(key.to_sym rescue key) || key] = symbolizer.call(value)
25
15
  result
26
16
  end
27
- end
28
-
29
- def deep_symbolize_keys!
30
- replace(deep_symbolize_keys)
31
- end
17
+ end unless instance_methods.include?(:deep_symbolize_keys)
32
18
 
33
19
  # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
34
20
  def deep_merge(other)
35
21
  merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
36
22
  merge(other, &merger)
37
- end unless Hash.method_defined?(:deep_merge)
23
+ end unless instance_methods.include?(:deep_merge)
38
24
  end
@@ -0,0 +1,13 @@
1
+ class Hashr
2
+ module Delegate
3
+ module Conditional
4
+ def method_missing(name, *args, &block)
5
+ if (args.any? || block) && @data.respond_to?(name)
6
+ @data.send(name, *args, &block)
7
+ else
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ class Hashr
2
+ module Delegation
3
+ module Hash
4
+ METHODS = [
5
+ :all?,
6
+ :any?,
7
+ :clear,
8
+ :delete,
9
+ :delete_if,
10
+ :detect,
11
+ :drop,
12
+ :drop_while,
13
+ :each,
14
+ :empty?,
15
+ :fetch,
16
+ :find,
17
+ :flat_map,
18
+ :grep,
19
+ :group_by,
20
+ :hash,
21
+ :inject,
22
+ :invert,
23
+ :is_a?,
24
+ :keep_if,
25
+ :key,
26
+ :key?,
27
+ :keys,
28
+ :length,
29
+ :map,
30
+ :merge,
31
+ :nil?,
32
+ :none?,
33
+ :one?,
34
+ :reduce,
35
+ :reject,
36
+ :select,
37
+ :size,
38
+ :value?,
39
+ :values,
40
+ :values_at
41
+ ]
42
+
43
+ METHODS.each do |name|
44
+ define_method(name) { |*args, &block| @data.send(name, *args, &block }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ class Hashr
2
+ module Env
3
+ class Vars < Struct.new(:defaults, :namespace)
4
+ FALSE = [false, nil, 'false', 'nil', '']
5
+
6
+ def to_h
7
+ defaults.deep_merge(read_env(defaults, namespace.dup))
8
+ end
9
+
10
+ private
11
+
12
+ def read_env(defaults, namespace)
13
+ defaults.inject({}) do |result, (key, default)|
14
+ keys = namespace + [key]
15
+ value = default.is_a?(Hash) ? read_env(default, keys) : var(keys, default)
16
+ result.merge(key => value)
17
+ end
18
+ end
19
+
20
+ def var(keys, default)
21
+ key = keys.map(&:upcase).join('_')
22
+ value = ENV.fetch(key, default)
23
+ cast(value, default, keys)
24
+ end
25
+
26
+ def cast(value, default, keys)
27
+ case default
28
+ when Array
29
+ value.respond_to?(:split) ? value.split(',') : Array(value)
30
+ when true, false
31
+ not FALSE.include?(value)
32
+ else
33
+ value
34
+ end
35
+ end
36
+
37
+ def namespace
38
+ Array(super && super.upcase)
39
+ end
40
+ end
41
+
42
+ attr_accessor :env_namespace
43
+
44
+ def defaults
45
+ Vars.new(super, env_namespace).to_h
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
- class Hashr < Hash
2
- VERSION = '1.0.0'
1
+ class Hashr < BasicObject
2
+ VERSION = '2.0.0.rc1'
3
3
  end
metadata CHANGED
@@ -1,77 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-04 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: test_declarative
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 0.0.2
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: 0.0.2
41
- - !ruby/object:Gem::Dependency
42
- name: minitest
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: 5.0.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: 5.0.0
11
+ date: 2015-10-07 00:00:00.000000000 Z
12
+ dependencies: []
55
13
  description: Simple Hash extension to make working with nested hashes (e.g. for configuration)
56
14
  easier and less error-prone.
57
- email: svenfuchs@artweb-design.de
15
+ email: me@svenfuchs.com
58
16
  executables: []
59
17
  extensions: []
60
18
  extra_rdoc_files: []
61
19
  files:
62
20
  - Gemfile
63
- - Gemfile.lock
64
21
  - MIT-LICENSE
65
22
  - README.md
66
- - Rakefile
67
- - hashr.gemspec
68
23
  - lib/hashr.rb
69
24
  - lib/hashr/core_ext/ruby/hash.rb
70
- - lib/hashr/env_defaults.rb
25
+ - lib/hashr/delegate/conditional.rb
26
+ - lib/hashr/delegate/hash.rb
27
+ - lib/hashr/env.rb
71
28
  - lib/hashr/version.rb
72
- - test/core_ext_test.rb
73
- - test/hashr_test.rb
74
- - test/test_helper.rb
75
29
  homepage: http://github.com/svenfuchs/hashr
76
30
  licenses: []
77
31
  metadata: {}
@@ -86,9 +40,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
40
  version: '0'
87
41
  required_rubygems_version: !ruby/object:Gem::Requirement
88
42
  requirements:
89
- - - ">="
43
+ - - ">"
90
44
  - !ruby/object:Gem::Version
91
- version: '0'
45
+ version: 1.3.1
92
46
  requirements: []
93
47
  rubyforge_project: "[none]"
94
48
  rubygems_version: 2.4.5
@@ -1,21 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- hashr (0.0.22)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- minitest (5.3.4)
10
- rake (0.9.2)
11
- test_declarative (0.0.5)
12
-
13
- PLATFORMS
14
- java
15
- ruby
16
-
17
- DEPENDENCIES
18
- hashr!
19
- minitest (>= 5.0.0)
20
- rake
21
- test_declarative (>= 0.0.2)
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- require 'rake'
2
- require 'rake/testtask'
3
-
4
- Rake::TestTask.new do |t|
5
- t.libs << 'lib' << 'test'
6
- # this currently does not work in rake 0.9.2, 0.9.3 will fix it
7
- # t.pattern = 'test/**/*_test.rb'
8
- t.test_files = FileList['test/**/*_test.rb']
9
- t.verbose = false
10
- end
11
-
12
- task :default => :test
@@ -1,23 +0,0 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift File.expand_path('../lib', __FILE__)
4
- require 'hashr/version'
5
-
6
- Gem::Specification.new do |s|
7
- s.name = "hashr"
8
- s.version = Hashr::VERSION
9
- s.authors = ["Sven Fuchs"]
10
- s.email = "svenfuchs@artweb-design.de"
11
- s.homepage = "http://github.com/svenfuchs/hashr"
12
- s.summary = "Simple Hash extension to make working with nested hashes (e.g. for configuration) easier and less error-prone"
13
- s.description = "Simple Hash extension to make working with nested hashes (e.g. for configuration) easier and less error-prone."
14
-
15
- s.files = Dir['{lib/**/*,test/**/*,[A-Z]*}']
16
- s.platform = Gem::Platform::RUBY
17
- s.require_path = 'lib'
18
- s.rubyforge_project = '[none]'
19
-
20
- s.add_development_dependency 'rake'
21
- s.add_development_dependency 'test_declarative', '>=0.0.2'
22
- s.add_development_dependency 'minitest', '>=5.0.0'
23
- end
@@ -1,31 +0,0 @@
1
- class Hashr < Hash
2
- module EnvDefaults
3
- attr_writer :env_namespace
4
-
5
- def env_namespace=(env_namespace)
6
- @env_namespace = [env_namespace.upcase]
7
- end
8
-
9
- def env_namespace
10
- @env_namespace ||= []
11
- end
12
-
13
- def definition
14
- deep_enverize(super)
15
- end
16
-
17
- protected
18
-
19
- def deep_enverize(hash, nesting = env_namespace)
20
- hash.each do |key, value|
21
- nesting << key.to_s.upcase
22
- hash[key] = case value
23
- when Hash
24
- deep_enverize(value, nesting)
25
- else
26
- ENV.fetch(nesting.join('_'), value)
27
- end.tap { nesting.pop }
28
- end
29
- end
30
- end
31
- end
@@ -1,28 +0,0 @@
1
- require 'test_helper'
2
-
3
- class CoreExtTest < Minitest::Test
4
- test 'Hash#deep_symbolize_keys walks arrays, too' do
5
- hash = { 'foo' => [{ 'bar' => 'bar', 'baz' => { 'buz' => 'buz' } }] }
6
- expected = { :foo => [{ :bar => 'bar', :baz => { :buz => 'buz' } }] }
7
- assert_equal expected, hash.deep_symbolize_keys
8
- end
9
-
10
- test 'Hash#deep_symbolize_keys! replaces with deep_symbolize' do
11
- hash = { 'foo' => { 'bar' => 'baz' } }
12
- expected = { :foo => { :bar => 'baz' } }
13
- hash.deep_symbolize_keys!
14
- assert_equal expected, hash
15
- end
16
-
17
- test 'Hash#slice returns a new hash containing the given keys' do
18
- hash = { :foo => 'foo', :bar => 'bar', :baz => 'baz' }
19
- expected = { :foo => 'foo', :bar => 'bar' }
20
- assert_equal expected, hash.slice(:foo, :bar)
21
- end
22
-
23
- test 'Hash#slice does not explode on a missing key' do
24
- hash = {}
25
- expected = {}
26
- assert_equal expected, hash.slice(:foo)
27
- end
28
- end
@@ -1,230 +0,0 @@
1
- require 'test_helper'
2
-
3
- class HashrTest < Minitest::Test
4
- def teardown
5
- Hashr.raise_missing_keys = false
6
- end
7
-
8
- test 'initialize takes nil' do
9
- Hashr.new(nil)
10
- end
11
-
12
- test 'initialize raises an ArgumentError when given a string' do
13
- assert_raises(ArgumentError) { Hashr.new("foo") }
14
- end
15
-
16
- test 'method access on an existing key returns the value' do
17
- assert_equal 'foo', Hashr.new(:foo => 'foo').foo
18
- end
19
-
20
- test 'method access on a non-existing key returns nil when raise_missing_keys is false' do
21
- Hashr.raise_missing_keys = false
22
- assert_nil Hashr.new(:foo => 'foo').bar
23
- end
24
-
25
- test 'method access on a non-existing key raises an IndexError when raise_missing_keys is true' do
26
- Hashr.raise_missing_keys = true
27
- assert_raises(IndexError) { Hashr.new(:foo => 'foo').bar }
28
- end
29
-
30
- test 'method access on an existing nested key returns the value' do
31
- assert_equal 'bar', Hashr.new(:foo => { :bar => 'bar' }).foo.bar
32
- end
33
-
34
- test 'method access on a non-existing nested key returns nil when raise_missing_keys is false' do
35
- Hashr.raise_missing_keys = false
36
- assert_nil Hashr.new(:foo => { :bar => 'bar' }).foo.baz
37
- end
38
-
39
- test 'method access on a non-existing nested key raises an IndexError when raise_missing_keys is true' do
40
- Hashr.raise_missing_keys = true
41
- assert_raises(IndexError) { Hashr.new(:foo => { :bar => 'bar' }).foo.baz }
42
- end
43
-
44
- test 'method access with a question mark returns true if the key has a value' do
45
- assert_equal true, Hashr.new(:foo => { :bar => 'bar' }).foo.bar?
46
- end
47
-
48
- test 'method access with a question mark returns false if the key does not have a value' do
49
- assert_equal false, Hashr.new(:foo => { :bar => 'bar' }).foo.baz?
50
- end
51
-
52
- test 'hash access is indifferent about symbols/strings (string data given, symbol keys used)' do
53
- assert_equal 'bar', Hashr.new('foo' => { 'bar' => 'bar' })[:foo][:bar]
54
- end
55
-
56
- test 'hash access is indifferent about symbols/strings (symbol data given, string keys used)' do
57
- assert_equal 'bar', Hashr.new(:foo => { :bar => 'bar' })['foo']['bar']
58
- end
59
-
60
- test 'mixing symbol and string keys in defaults and data' do
61
- Symbolized = Class.new(Hashr) { define :foo => 'foo' }
62
- Stringified = Class.new(Hashr) { define 'foo' => 'foo' }
63
- NoDefault = Class.new(Hashr)
64
-
65
- assert_equal 'foo', Symbolized.new.foo
66
- assert_equal 'foo', Stringified.new.foo
67
- assert_nil NoDefault.new.foo
68
-
69
- assert_equal 'foo', Symbolized.new(:foo => 'foo').foo
70
- assert_equal 'foo', Stringified.new(:foo => 'foo').foo
71
- assert_equal 'foo', NoDefault.new(:foo => 'foo').foo
72
-
73
- assert_equal 'foo', Symbolized.new('foo' => 'foo').foo
74
- assert_equal 'foo', Stringified.new('foo' => 'foo').foo
75
- assert_equal 'foo', NoDefault.new('foo' => 'foo').foo
76
- end
77
-
78
- test 'method assignment works' do
79
- hashr = Hashr.new
80
- hashr.foo = 'foo'
81
- assert_equal 'foo', hashr.foo
82
- end
83
-
84
- test 'method using a string key works' do
85
- hashr = Hashr.new
86
- hashr['foo'] = 'foo'
87
- assert_equal 'foo', hashr.foo
88
- end
89
-
90
- test 'using a symbol key works' do
91
- hashr = Hashr.new
92
- hashr[:foo] = 'foo'
93
- assert_equal 'foo', hashr.foo
94
- end
95
-
96
- test 'respond_to? returns true if raise_missing_keys is off' do
97
- Hashr.raise_missing_keys = false
98
- hashr = Hashr.new
99
- assert hashr.respond_to?(:foo)
100
- end
101
-
102
- test 'respond_to? returns false for missing keys if raise_missing_keys is on' do
103
- Hashr.raise_missing_keys = true
104
- hashr = Hashr.new
105
- assert_equal false, hashr.respond_to?(:foo)
106
- end
107
-
108
- test 'respond_to? returns true for extant keys if raise_missing_keys is on' do
109
- Hashr.raise_missing_keys = true
110
- hashr = Hashr.new
111
- hashr[:foo] = 'bar'
112
- assert hashr.respond_to?(:foo)
113
- end
114
-
115
- test 'defining defaults' do
116
- klass = Class.new(Hashr) do
117
- define :foo => 'foo', :bar => { :baz => 'baz' }
118
- end
119
- assert_equal 'foo', klass.new.foo
120
- assert_equal 'baz', klass.new.bar.baz
121
- end
122
-
123
- test 'defining different defaults on different classes ' do
124
- foo = Class.new(Hashr) { define :foo => 'foo' }
125
- bar = Class.new(Hashr) { define :bar => 'bar' }
126
-
127
- assert_equal 'foo', foo.definition[:foo]
128
- assert_equal 'bar', bar.definition[:bar]
129
- end
130
-
131
- test 'defining different env_namespaces on different classes ' do
132
- foo = Class.new(Hashr) {extend Hashr::EnvDefaults; self.env_namespace = 'foo' }
133
- bar = Class.new(Hashr) {extend Hashr::EnvDefaults; self.env_namespace = 'bar' }
134
-
135
- assert_equal ['FOO'], foo.env_namespace
136
- assert_equal ['BAR'], bar.env_namespace
137
- end
138
-
139
- test 'defaults to env vars' do
140
- klass = Class.new(Hashr) do
141
- extend Hashr::EnvDefaults
142
- self.env_namespace = 'hashr'
143
- define :foo => 'foo', :bar => { :baz => 'baz' }
144
- end
145
-
146
- ENV['HASHR_FOO'] = 'env foo'
147
- ENV['HASHR_BAR_BAZ'] = 'env bar baz'
148
-
149
- hashr = klass.new
150
- assert_equal 'env foo', hashr.foo
151
- assert_equal 'env bar baz', hashr.bar.baz
152
-
153
- # ENV.delete('HASHR_FOO')
154
- # ENV.delete('HASHR_BAR_BAZ')
155
-
156
- # hashr = klass.new
157
- # assert_equal 'foo', hashr.foo
158
- # assert_equal 'bar baz', hashr.bar.baz
159
- end
160
-
161
- test 'a key :_include includes the given modules' do
162
- klass = Class.new(Hashr) do
163
- define :foo => { :_include => Module.new { def helper; 'helper'; end } }
164
- end
165
- hashr = klass.new(:foo => { 'helper' => 'foo' })
166
- assert_equal 'helper', klass.new(:foo => { 'helper' => 'foo' }).foo.helper
167
- end
168
-
169
- test 'a key :_include includes the given modules (using defaults)' do
170
- klass = Class.new(Hashr) do
171
- define :foo => { :_include => Module.new { def helper; 'helper'; end } }
172
- end
173
- assert_equal 'helper', klass.new.foo.helper
174
- end
175
-
176
- test 'a key :_access includes an anonymous module with accessors' do
177
- klass = Class.new(Hashr) do
178
- define :foo => { :_access => [:key] }
179
- end
180
-
181
- assert_equal 'key', klass.new(:foo => { :key => 'key' }).foo.key
182
- end
183
-
184
- test 'defining defaults always also makes sure an accessor is used' do
185
- klass = Class.new(Hashr) do
186
- define :foo => { :default => 'default' }
187
- end
188
-
189
- assert_equal 'default', klass.new().foo.default
190
- end
191
-
192
- test 'all: allows to define :_include modules which will be included into all nested hashes' do
193
- klass = Class.new(Hashr) do
194
- default :_include => Module.new { def helper; 'helper'; end }
195
- end
196
- assert_equal 'helper', klass.new.helper
197
- assert_equal 'helper', klass.new(:foo => { :bar => {} }).foo.bar.helper
198
- end
199
-
200
- test 'all: allows to define :_access which will include an anonymous module with accessors into all nested hashes' do
201
- klass = Class.new(Hashr) do
202
- default :_access => :key
203
- end
204
- assert_equal 'key', klass.new(:key => 'key').key
205
- assert_equal 'key', klass.new(:foo => { :bar => { :key => 'key' } }).foo.bar.key
206
- end
207
-
208
- test 'anonymously overwriting core Hash methods' do
209
- hashr = Hashr.new(:count => 5) do
210
- def count
211
- self[:count]
212
- end
213
- end
214
- assert_equal 5, hashr.count
215
- end
216
-
217
- test 'to_hash converts the Hashr instance and all of its children to Hashes' do
218
- hash = Hashr.new(:foo => { :bar => { :baz => 'baz' } }).to_hash
219
-
220
- assert hash.instance_of?(Hash)
221
- assert hash[:foo].instance_of?(Hash)
222
- assert hash[:foo][:bar].instance_of?(Hash)
223
- end
224
-
225
- test 'set sets a dot separated path to nested hashes' do
226
- hashr = Hashr.new(:foo => { :bar => 'bar' })
227
- hashr.set('foo.baz', 'baz')
228
- assert_equal 'baz', hashr.foo.baz
229
- end
230
- end
@@ -1,4 +0,0 @@
1
- require 'bundler/setup'
2
- require 'minitest/autorun'
3
- require 'test_declarative'
4
- require 'hashr'