hash_pick 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71c464d01eac460c7648f25a75ed9934f0edf762
4
- data.tar.gz: 2b1f5e47a4625660f7558960d01bcf80eaa183ae
3
+ metadata.gz: 930eee6958a5e12f3706b0c47b2d8b36e8209a94
4
+ data.tar.gz: f3bbcca070e3d83dfba08d4286da1747bde8b6ed
5
5
  SHA512:
6
- metadata.gz: ebe4a4af2e2f4b918883ec0ecd779b55bbfcf723895da8ca19b29edd982257ce476ff9c457bc0db6d0f5c4d44d002294937515009e6aef7bf50ca7ca1d976d1a
7
- data.tar.gz: 8e78365148d86b93a711b624fb40b56a4567c24a8708b5d531414076a3a81192c2d3175fc222f1588b2d2d819481b733d7449f1518b48526548870b4ad06252b
6
+ metadata.gz: d9473e4453f82608a60450281e1c4fce27b3e78bed49bef720a30102a9d691a267c69802c3af1cce88eabc0413e3e9ef74721710dae133bb3634e3fe882ae642
7
+ data.tar.gz: 5d1e2ad8ef12b33f3ca3aba8d6e33e7fe4802f94e30731899880ba93047ee7da28498ba10f86502251ed303abfcb837941525b7b9e5e44b2d6cac3815f96008a
data/README.rdoc CHANGED
@@ -7,7 +7,9 @@ Hash paths can be expressed as lists of keys of these types:
7
7
  * Object.
8
8
  * String.
9
9
  * Symbol.
10
- * Eithe String or Symbol.
10
+ * Either String or Symbol.
11
+
12
+ In addition, the general form of hash path query iteration is available in support of arbitrary path query semantics.
11
13
 
12
14
  The methods are provided by the module {HashPick}.
13
15
 
@@ -1,3 +1,3 @@
1
1
  module HashPick
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/hash_pick.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # Fetch a value from a nested dictionary
5
5
  #
6
6
  # Provides methods for fetching a value from a nested dictionary (an object that implements +include?+ and +fetch+),
7
- # using a key path expressed as a list (+Enumerable+).
7
+ # using a key path expressed as a list (an object that implements +inject+).
8
8
  #
9
9
  # The key path is iterated. In each iteration, the key is looked up in the dictionary, and the value found is used
10
10
  # as the dictionary for the next iteration. Lookup failure immediately returns nil.
@@ -45,22 +45,101 @@ module HashPick
45
45
  # an ordered list of keys that implement +to_s+ and +to_sym+.
46
46
  # @return [Object] the value found at the path in the dictionary.
47
47
  # @return [nil] if any key lookup failed.
48
+ # @raise [ArgumentError] if +hash+ isn't a dictionary.
49
+ # @raise [ArgumentError] if +path+ isn't a list.
50
+ # @raise [ArgumentError] if any key in +path+ is +nil+.
48
51
  #
49
52
  def self.indifferent(hash, path)
50
- path.inject(hash) do |acc, p|
51
- break unless acc.respond_to?(:include?) && acc.respond_to?(:fetch)
53
+ assert_non_nil_path_keys(path)
54
+
55
+ pick(hash, path) do |acc, p|
52
56
  if acc.include?(p.to_sym)
53
57
  acc.fetch(p.to_sym)
54
58
  elsif acc.include?(p.to_s)
55
59
  acc[p.to_s]
56
60
  else
57
- break
61
+ throw :break
62
+ end
63
+ end
64
+ end
65
+
66
+ ##
67
+ # General form of hash path iteration
68
+ #
69
+ # Passes to +block+, the dictionary and hash key for each iteration of the hash path, using
70
+ # the return value of the block as the dictionary for the next iteration, or as the return
71
+ # value of the last iteration. If the block throws +:break+, iteration is aborted.
72
+ #
73
+ # @example Complex hash path semantics
74
+ #
75
+ # require "hash_pick"
76
+ #
77
+ # dict = {
78
+ # live: true,
79
+ # sheldon: {
80
+ # live: true,
81
+ # first_name: "Sheldon",
82
+ # last_name: "Hearn",
83
+ # },
84
+ # charles: {
85
+ # first_name: "Charles",
86
+ # last_name: "Mulder",
87
+ # }
88
+ # }
89
+ #
90
+ # HashPick.pick(dict, [:sheldon, :first_name]) do |p, k|
91
+ # throw :break unless p[:live] and p.include?(k)
92
+ # p[k]
93
+ # end
94
+ # # => "Sheldon"
95
+ #
96
+ # HashPick.pick(dict, [:charles, :first_name]) do |p, k|
97
+ # throw :break unless p[:live] and p.include?(k)
98
+ # p[k]
99
+ # end
100
+ # # => "Hearn"
101
+ #
102
+ # @param [Hash] hash
103
+ # the dictionary to apply the +path+ to.
104
+ # @param [Array] path
105
+ # an ordered list of path keys.
106
+ # @return [Object] the value of the last iteration.
107
+ # @return [nil] if +block+ throws +:break+ in any iteration.
108
+ # @raise [ArgumentError] if +hash+ isn't a dictionary.
109
+ # @raise [ArgumentError] if +path+ isn't a list.
110
+ #
111
+ def self.pick(hash, path, &block)
112
+ assert_dictionary(hash)
113
+ assert_enumerable_path(path)
114
+ catch(:break) do
115
+ path.inject(hash) do |acc, p|
116
+ break unless dictionary?(acc)
117
+ block.call(acc, p)
58
118
  end
59
119
  end
60
120
  end
61
121
 
62
122
  class << self
63
123
  alias_method :[], :indifferent
124
+
125
+ private
126
+
127
+ def assert_dictionary(hash)
128
+ raise ArgumentError.new("hash is not a dictionary") unless dictionary?(hash)
129
+ end
130
+
131
+ def dictionary?(hash)
132
+ hash.respond_to?(:include?) && hash.respond_to?(:fetch)
133
+ end
134
+
135
+ def assert_non_nil_path_keys(path)
136
+ raise ArgumentError.new("nil key in path") if path.any? { |p| p.nil? }
137
+ end
138
+
139
+ def assert_enumerable_path(path)
140
+ raise ArgumentError.new("path is not enumerable") unless path.respond_to?(:inject)
141
+ end
142
+
64
143
  end
65
144
 
66
145
  ##
@@ -74,12 +153,11 @@ module HashPick
74
153
  # an ordered list of object keys.
75
154
  # @return [Object] the value found at the path in the dictionary.
76
155
  # @return [nil] if any key lookup failed.
156
+ # @raise [ArgumentError] if +hash+ isn't a dictionary.
157
+ # @raise [ArgumentError] if +path+ isn't a list.
77
158
  #
78
159
  def self.object(hash, path)
79
- path.inject(hash) do |acc, p|
80
- break unless acc.respond_to?(:include?) && acc.respond_to?(:fetch) && acc.include?(p)
81
- acc[p]
82
- end
160
+ pick(hash, path) { |acc, p| acc[p] }
83
161
  end
84
162
 
85
163
  ##
@@ -93,8 +171,12 @@ module HashPick
93
171
  # an ordered list of keys that implement +to_sym+.
94
172
  # @return [Object] the value found at the path in the dictionary.
95
173
  # @return [nil] if any key lookup failed.
174
+ # @raise [ArgumentError] if +hash+ isn't a dictionary.
175
+ # @raise [ArgumentError] if +path+ isn't a list.
176
+ # @raise [ArgumentError] if any key in +path+ is +nil+.
96
177
  #
97
178
  def self.symbol(hash, path)
179
+ assert_non_nil_path_keys(path)
98
180
  object(hash, path.map(&:to_sym))
99
181
  end
100
182
 
@@ -109,8 +191,12 @@ module HashPick
109
191
  # an ordered list of keys that implement +to_s+.
110
192
  # @return [Object] the value found at the path in the dictionary.
111
193
  # @return [nil] if any key lookup failed.
194
+ # @raise [ArgumentError] if +hash+ isn't a dictionary.
195
+ # @raise [ArgumentError] if +path+ isn't a list.
196
+ # @raise [ArgumentError] if any key in +path+ is +nil+.
112
197
  #
113
198
  def self.string(hash, path)
199
+ assert_non_nil_path_keys(path)
114
200
  object(hash, path.map(&:to_s))
115
201
  end
116
202
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hash_pick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sheldon Hearn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-13 00:00:00.000000000 Z
11
+ date: 2017-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler