hash_pick 0.1.1 → 0.2.0

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: 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