Class: Hm
- Inherits:
-
Object
- Object
- Hm
- Defined in:
- lib/hm.rb,
lib/hm/dig.rb,
lib/hm/algo.rb,
lib/hm/version.rb
Overview
‘Hm` is a wrapper for chainable, terse, idiomatic Hash modifications.
Defined Under Namespace
Constant Summary collapse
- WILDCARD =
:*
- MAJOR =
0
- MINOR =
0
- PATCH =
4
- PRE =
nil
- VERSION =
[MAJOR, MINOR, PATCH, PRE].compact.join('.')
Instance Method Summary collapse
-
#bury(*path, value) ⇒ self
Stores value into deeply nested collection.
-
#cleanup ⇒ self
Removes all “empty” values and subcollections (‘nil`s, empty strings, hashes and arrays), including nested structures.
-
#compact ⇒ self
Removes all ‘nil` values, including nested structures.
-
#dig(*path) ⇒ Object
Like Ruby’s [#dig](docs.ruby-lang.org/en/2.4.0/Hash.html#method-i-dig), but supports wildcard key ‘:*` meaning “each item at this point”.
-
#dig!(*path) {|collection, path, rest| ... } ⇒ Object
Like #dig! but raises when key at any level is not found.
-
#except(*pathes) ⇒ self
Removes all specified pathes from input sequence.
-
#initialize(collection) ⇒ Hm
constructor
A new instance of Hm.
-
#partition(*pathes) {|value| ... } ⇒ Array<Hash>
Split hash into two: the one with the substructure matching ‘pathes`, and the with thos that do not.
-
#reduce(keys_to_keys) {|memo, value| ... } ⇒ self
Calculates one value from several values at specified pathes, using specified block.
-
#reject(*pathes) {|path, value| ... } ⇒ self
Drops subset of the collection by provided block (optionally looking only at pathes specified).
-
#select(*pathes) {|path, value| ... } ⇒ self
Select subset of the collection by provided block (optionally looking only at pathes specified).
-
#slice(*pathes) ⇒ self
Preserves only specified pathes from input sequence.
-
#to_h ⇒ Hash
(also: #to_hash)
Returns the result of all the processings inside the ‘Hm` object.
-
#transform(keys_to_keys, &processor) {|value| ... } ⇒ self
Renames input pathes to target pathes, with wildcard support.
-
#transform_keys(*pathes) {|key| ... } ⇒ self
Performs specified transformations on keys of input sequence, optionally limited only by specified pathes.
-
#transform_values(*pathes) {|value| ... } ⇒ self
Performs specified transformations on values of input sequence, limited only by specified pathes.
-
#update(keys_to_keys, &processor) {|value| ... } ⇒ self
Like #transform, but copies values instead of moving them (original keys/values are preserved).
-
#visit(*path, not_found: ->(*) {}) {|collection, path, value| ... } ⇒ self
Low-level collection walking mechanism.
Constructor Details
Instance Method Details
#bury(*path, value) ⇒ self
Stores value into deeply nested collection. ‘path` supports wildcards (“store at each matched path”) the same way #dig and other methods do. If specified path does not exists, it is created, with a “rule of thumb”: if next key is Integer, Array is created, otherwise it is Hash.
Caveats:
-
when ‘:*`-referred path does not exists, just `:*` key is stored;
-
as most of transformational methods, ‘bury` does not created and tested to work with `Struct`.
116 117 118 119 120 121 122 |
# File 'lib/hm.rb', line 116 def bury(*path, value) Algo.visit( @hash, path, not_found: ->(at, pth, rest) { at[pth.last] = Algo.nest_hashes(value, *rest) } ) { |at, pth, _| at[pth.last] = value } self end |
#cleanup ⇒ self
Removes all “empty” values and subcollections (‘nil`s, empty strings, hashes and arrays), including nested structures. Empty subcollections are removed recoursively.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/hm.rb', line 332 def cleanup deletions = -1 # We do several runs to delete recursively: {a: {b: [nil]}} # first: {a: {b: []}} # second: {a: {}} # third: {} # More effective would be some "inside out" visiting, probably until deletions.zero? deletions = 0 Algo.visit_all(@hash) do |at, path, val| if val.nil? || val.respond_to?(:empty?) && val.empty? deletions += 1 Algo.delete(at, path.last) end end end self end |
#compact ⇒ self
Removes all ‘nil` values, including nested structures.
317 318 319 320 321 |
# File 'lib/hm.rb', line 317 def compact Algo.visit_all(@hash) do |at, path, val| Algo.delete(at, path.last) if val.nil? end end |
#dig(*path) ⇒ Object
Like Ruby’s [#dig](docs.ruby-lang.org/en/2.4.0/Hash.html#method-i-dig), but supports wildcard key ‘:*` meaning “each item at this point”.
Each level of data structure should have ‘#dig` method, otherwise `TypeError` is raised.
54 55 56 |
# File 'lib/hm.rb', line 54 def dig(*path) Algo.visit(@hash, path) { |_, _, val| val } end |
#dig!(*path) {|collection, path, rest| ... } ⇒ Object
Like #dig! but raises when key at any level is not found. This behavior can be changed by passed block.
79 80 81 82 83 |
# File 'lib/hm.rb', line 79 def dig!(*path, ¬_found) not_found ||= ->(_, pth, _) { fail KeyError, "Key not found: #{pth.map(&:inspect).join('/')}" } Algo.visit(@hash, path, not_found: not_found) { |_, _, val| val } end |
#except(*pathes) ⇒ self
Removes all specified pathes from input sequence.
282 283 284 285 286 287 |
# File 'lib/hm.rb', line 282 def except(*pathes) pathes.each do |path| Algo.visit(@hash, path) { |what, pth, _| Algo.delete(what, pth.last) } end self end |
#partition(*pathes) {|value| ... } ⇒ Array<Hash>
Split hash into two: the one with the substructure matching ‘pathes`, and the with thos that do not.
457 458 459 460 461 462 463 464 |
# File 'lib/hm.rb', line 457 def partition(*pathes) # FIXME: this implementation is naive, it performs 2 additional deep copies and 2 full cycles of # visiting instead of just splitting existing data in one pass. It works, though [ Hm(@hash).slice(*pathes).to_h, Hm(@hash).except(*pathes).to_h ] end |
#reduce(keys_to_keys) {|memo, value| ... } ⇒ self
Calculates one value from several values at specified pathes, using specified block.
435 436 437 438 439 440 |
# File 'lib/hm.rb', line 435 def reduce(keys_to_keys, &block) keys_to_keys.each do |from, to| bury(*to, dig(*from).reduce(&block)) end self end |
#reject(*pathes) {|path, value| ... } ⇒ self
Drops subset of the collection by provided block (optionally looking only at pathes specified).
405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/hm.rb', line 405 def reject(*pathes) if pathes.empty? Algo.visit_all(@hash) do |at, path, val| Algo.delete(at, path.last) if yield(path, val) end else pathes.each do |path| Algo.visit(@hash, path) do |at, pth, val| Algo.delete(at, pth.last) if yield(pth, val) end end end self end |
#select(*pathes) {|path, value| ... } ⇒ self
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/hm.rb', line 370 def select(*pathes) res = Hm.new({}) if pathes.empty? Algo.visit_all(@hash) do |_, path, val| res.bury(*path, val) if yield(path, val) end else pathes.each do |path| Algo.visit(@hash, path) do |_, pth, val| res.bury(*pth, val) if yield(pth, val) end end end @hash = res.to_h self end |
#slice(*pathes) ⇒ self
Preserves only specified pathes from input sequence.
300 301 302 303 304 305 306 307 |
# File 'lib/hm.rb', line 300 def slice(*pathes) result = Hm.new({}) pathes.each do |path| Algo.visit(@hash, path) { |_, new_path, val| result.bury(*new_path, val) } end @hash = result.to_h self end |
#to_h ⇒ Hash Also known as: to_hash
Returns the result of all the processings inside the ‘Hm` object.
Note, that you can pass an Array as a top-level structure to ‘Hm`, and in this case `to_h` will return the processed Array… Not sure what to do about that currently.
472 473 474 |
# File 'lib/hm.rb', line 472 def to_h @hash end |
#transform(keys_to_keys, &processor) {|value| ... } ⇒ self
Currently, only one wildcard per each from and to pattern is supported.
Renames input pathes to target pathes, with wildcard support.
171 172 173 174 |
# File 'lib/hm.rb', line 171 def transform(keys_to_keys, &processor) keys_to_keys.each { |from, to| transform_one(Array(from), Array(to), &processor) } self end |
#transform_keys(*pathes) {|key| ... } ⇒ self
Performs specified transformations on keys of input sequence, optionally limited only by specified pathes.
Note that when ‘pathes` parameter is passed, only keys directly matching the pathes are processed, not entire sub-collection under this path.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/hm.rb', line 219 def transform_keys(*pathes) if pathes.empty? Algo.visit_all(@hash) do |at, path, val| if at.is_a?(Hash) at.delete(path.last) at[yield(path.last)] = val end end else pathes.each do |path| Algo.visit(@hash, path) do |at, pth, val| Algo.delete(at, pth.last) at[yield(pth.last)] = val end end end self end |
#transform_values(*pathes) {|value| ... } ⇒ self
Performs specified transformations on values of input sequence, limited only by specified pathes.
If no ‘pathes` are passed, all “terminal” values (e.g. not diggable) are yielded and transformed.
256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/hm.rb', line 256 def transform_values(*pathes) if pathes.empty? Algo.visit_all(@hash) do |at, pth, val| at[pth.last] = yield(val) unless Dig.diggable?(val) end else pathes.each do |path| Algo.visit(@hash, path) { |at, pth, val| at[pth.last] = yield(val) } end end self end |
#update(keys_to_keys, &processor) {|value| ... } ⇒ self
Like #transform, but copies values instead of moving them (original keys/values are preserved).
192 193 194 195 |
# File 'lib/hm.rb', line 192 def update(keys_to_keys, &processor) keys_to_keys.each { |from, to| transform_one(Array(from), Array(to), remove: false, &processor) } self end |