tash 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tash.rb ADDED
@@ -0,0 +1,1230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'tash/version'
6
+
7
+ # A Tash is a hash with transformed keys.
8
+ class Tash
9
+ extend Forwardable
10
+ include Enumerable
11
+
12
+ def self.current_ruby_version
13
+ @current_ruby_version ||= RUBY_VERSION[/\A(\d+\.\d+)/, 1]
14
+ end
15
+ private_class_method :current_ruby_version
16
+
17
+ # Returns a new Tash object populated with the given objects, if any. If
18
+ # a Tash is passed with no block it returns a duplicate with the
19
+ # transform block from the original. If a Tash is passed with a block it is
20
+ # treated as a Hash.
21
+ #
22
+ # @example Empty
23
+ # Tash[] # => {}
24
+ #
25
+ # @example Given a Tash with no block
26
+ # t = Tash[FOO: 1, BAR: 2, &:downcase]
27
+ # t2 = Tash[t]
28
+ # t2[:BAZ] = 3
29
+ # t2 # => {:foo=>1, :bar=>2, :baz=>3}
30
+ #
31
+ # @example Given a Hash
32
+ # Tash[FOO: 1, BAR: 2, &:downcase] # => {:foo=>1, :bar=>2}
33
+ #
34
+ # @example Given an even number of objects
35
+ # Tash[:FOO, 1, :BAR, 2, &:downcase] # => {:foo=>1, :bar=>2}
36
+ #
37
+ # @param *objects [Array<Objects>] A Tash, Hash, or even number of objects
38
+ #
39
+ # @return [Tash]
40
+ def self.[](*objects, &transform) # rubocop:disable Metrics/PerceivedComplexity
41
+ if objects.empty?
42
+ new(&transform)
43
+ elsif objects.size == 1 && !transform && objects.first.is_a?(self)
44
+ objects.first.dup
45
+ elsif objects.size == 1 && objects.first.respond_to?(:to_hash)
46
+ from_hash(objects.first.to_hash, &transform)
47
+ elsif objects.size.even?
48
+ from_array(objects, &transform)
49
+ else
50
+ raise ArgumentError, "odd number of arguments for #{name}"
51
+ end
52
+ end
53
+
54
+ def self.from_hash(hash, &transform)
55
+ hash.each_with_object(new(&transform)) do |(k, v), tash|
56
+ tash[k] = v
57
+ end
58
+ end
59
+ private_class_method :from_hash
60
+
61
+ def self.from_array(array, &transform)
62
+ array.each_slice(2).with_object(new(&transform)) do |(k, v), tash|
63
+ tash[k] = v
64
+ end
65
+ end
66
+ private_class_method :from_array
67
+
68
+ # Returns a new empty Tash object.
69
+ #
70
+ # @example
71
+ # Tash.new { |key| key.to_s.downcase }
72
+ #
73
+ # @param transform [Proc] receives a key and transforms it as desired
74
+ # before using the key
75
+ #
76
+ # @return [Tash]
77
+ def initialize(&transform)
78
+ @transform = transform
79
+ @ir = {} # internal representation - @ir[transformed key] = value
80
+ @default_proc = nil
81
+ end
82
+
83
+ def_delegators :@ir,
84
+ :<,
85
+ :<=,
86
+ :>,
87
+ :>=,
88
+ :compare_by_identity,
89
+ :default=,
90
+ :empty?,
91
+ :flatten,
92
+ :hash,
93
+ :inspect,
94
+ :key,
95
+ :keys,
96
+ :rassoc,
97
+ :rehash,
98
+ :shift,
99
+ :size,
100
+ :to_a,
101
+ :value?,
102
+ :values
103
+
104
+ alias has_value? value?
105
+ alias length size
106
+ alias to_s inspect
107
+
108
+ # @!method < other
109
+ # Returns `true` if tash is a proper subset of other, `false` otherwise.
110
+ #
111
+ # @example
112
+ # t1 = Tash[foo: 0, bar: 1]
113
+ # t2 = Tash[foo: 0, bar: 1, baz: 2]
114
+ # t1 < t2 # => true
115
+ # t2 < t1 # => false
116
+ # t1 < t1 # => false
117
+ #
118
+ # @param other [Tash, Hash]
119
+ #
120
+ # @return [true or false]
121
+
122
+ # @!method <= other
123
+ # Returns `true` if tash is a subset of other, `false` otherwise.
124
+ #
125
+ # @example
126
+ # t1 = Tash[foo: 0, bar: 1]
127
+ # t2 = Tash[foo: 0, bar: 1, baz: 2]
128
+ # t1 <= t2 # => true
129
+ # t2 <= t1 # => false
130
+ # t1 <= t1 # => true
131
+ #
132
+ # @param other [Tash, Hash]
133
+ #
134
+ # @return [true or false]
135
+
136
+ # Returns `true` if all of the following are true:
137
+ #
138
+ # * object is a Tash object.
139
+ # * tash and object have the same keys (regardless of order).
140
+ # * For each key `key`, `tash[key] == other[key]`.
141
+ #
142
+ # Otherwise, returns `false`.
143
+ #
144
+ # @example
145
+ # t1 = Tash[foo: 0, bar: 1, baz: 2]
146
+ # t2 = Tash[foo: 0, bar: 1, baz: 2]
147
+ # t1 == t2 # => true
148
+ # h3 = Tash[baz: 2, bar: 1, foo: 0]
149
+ # t1 == h3 # => true
150
+ #
151
+ # @param other [Object]
152
+ #
153
+ # @return [true or false]
154
+ def ==(other)
155
+ return false unless other.is_a?(self.class)
156
+
157
+ @ir == other.to_hash
158
+ end
159
+
160
+ # @!method > other
161
+ # Returns `true` if tash is a proper superset of other, `false` otherwise.
162
+ #
163
+ # @example
164
+ # t1 = Tash[foo: 0, bar: 1, baz: 2]
165
+ # t2 = Tash[foo: 0, bar: 1]
166
+ # t1 > t2 # => true
167
+ # t2 > t1 # => false
168
+ # t1 > t1 # => false
169
+ #
170
+ # @param other [Tash, Hash]
171
+ #
172
+ # @return [true or false]
173
+
174
+ # @!method >= other
175
+ # Returns `true` if tash is a superset of other, `false` otherwise.
176
+ #
177
+ # @example
178
+ # t1 = Tash[foo: 0, bar: 1, baz: 2]
179
+ # t2 = Tash[foo: 0, bar: 1]
180
+ # t1 >= t2 # => true
181
+ # t2 >= t1 # => false
182
+ # t1 >= t1 # => true
183
+ #
184
+ # @param other [Tash, Hash]
185
+ #
186
+ # @return [true or false]
187
+
188
+ # Returns the value associated with the given `key` after transformation, if
189
+ # found.
190
+ #
191
+ # @example
192
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
193
+ # t[:FOO] # => 0
194
+ #
195
+ # @example Not found key with a default value
196
+ # t = Tash[foo: 0, bar: 1, baz: 2]
197
+ # t.default = 1_000
198
+ # t[:nosuch] # => 1_000
199
+ #
200
+ # @param key [Object]
201
+ #
202
+ # @return [value]
203
+ def [](key)
204
+ @ir[transform(key)]
205
+ end
206
+
207
+ # Associates the given `value` with the given `key` after transformation. If
208
+ # the given post transformation `key` exists, replaces its value with the
209
+ # given `value`; the ordering is not affected. If post transformation `key`
210
+ # does not exist, adds the transformed `key` and `value`; the new entry is
211
+ # last in the order.
212
+ #
213
+ # @example
214
+ # t = Tash[Foo: 0, Bar: 1, &:downcase]
215
+ # t[:FOO] = 2 # => 2
216
+ # t.store(:bar, 3) # => 3
217
+ # t[:Bat] = 4 # => 4
218
+ # t # => {:foo=>2, :bar=>3, :bat=>4}
219
+ #
220
+ # @param key [Object]
221
+ # @param value [Object]
222
+ #
223
+ # @return [value]
224
+ def []=(key, value)
225
+ @ir[transform(key)] = value
226
+ end
227
+ alias store []=
228
+
229
+ # If the given transformed `key` is found, returns a 2-element Array
230
+ # containing that key and its value. Returns `nil` if the tranformed key
231
+ # `key` is not found.
232
+ #
233
+ # @example
234
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
235
+ # t.assoc(:bar) # => [:bar, 1]
236
+ #
237
+ # @param key [Object]
238
+ #
239
+ # @return [Array<K,V> or nil]
240
+ def assoc(key)
241
+ @ir.assoc(transform(key))
242
+ end
243
+
244
+ # Removes all tash entries.
245
+ #
246
+ # @return [self]
247
+ def clear
248
+ @ir.clear
249
+ self
250
+ end
251
+
252
+ # Returns a copy of `self` with all `nil`-valued entries removed.
253
+ #
254
+ # @example
255
+ # t = Tash[foo: 0, bar: nil, baz: 2, bat: nil]
256
+ # t1 = t.compact
257
+ # t1 # => {:foo=>0, :baz=>2}
258
+ #
259
+ # @return [Tash]
260
+ def compact
261
+ new_from_self(@ir.compact)
262
+ end
263
+
264
+ # Returns `self` with all its `nil`-valued entries removed.
265
+ #
266
+ # @example
267
+ # t = Tash[foo: 0, bar: nil, baz: 2, bat: nil]
268
+ # t.compact! # => {:foo=>0, :baz=>2}
269
+ #
270
+ # @return [self or nil]
271
+ def compact!
272
+ self if @ir.compact!
273
+ end
274
+
275
+ # Sets `self` to consider only identity in comparing keys; two keys are
276
+ # considered the same only if they are the same object.
277
+ #
278
+ # @example Before being set
279
+ # s0 = 'x'
280
+ # s1 = 'x'
281
+ # t = Tash.new
282
+ # t.compare_by_identity? # => false
283
+ # t[s0] = 0
284
+ # t[s1] = 1
285
+ # t # => {"x"=>1}
286
+ #
287
+ # @example After being set
288
+ # t = Tash.new
289
+ # t.compare_by_identity # => {}
290
+ # t.compare_by_identity? # => true
291
+ # t[s0] = 0
292
+ # t[s1] = 1
293
+ # t # => {"x"=>0, "x"=>1}
294
+ #
295
+ # @return [self]
296
+ def compare_by_identity
297
+ @ir.compare_by_identity
298
+ self
299
+ end
300
+
301
+ # @!method compare_by_identity?
302
+ # Returns `true` if {#compare_by_identity} has been called, `false`
303
+ # otherwise.
304
+ #
305
+ # @return [Boolean]
306
+
307
+ # Provides support for pattern matching using a Tash. Pattern keys will be
308
+ # transformed before matching.
309
+ #
310
+ # @example
311
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
312
+ # case t
313
+ # in { foo: x, BAR: 1 }
314
+ # x
315
+ # else
316
+ # nil
317
+ # end # => 0
318
+ #
319
+ # @return [Hash]
320
+ def deconstruct_keys(keys)
321
+ return {} if keys.nil?
322
+
323
+ keys.to_h { |key| [key, self[key]] }
324
+ end
325
+
326
+ # @overload default
327
+ # @overload default(key)
328
+ #
329
+ # Returns the default value for the given transformed `key`. The returned
330
+ # value will be determined either by the default proc or by the default
331
+ # value.
332
+ #
333
+ # @example
334
+ # t = Tash.new
335
+ # t.default # => nil
336
+ #
337
+ # @example With a key
338
+ # t = Tash[Foo: 0]
339
+ # t.default_proc = proc { |tash, key| tash[k] = "No key #{key}" }
340
+ # t[:foo] = "Hello"
341
+ # t.default(:FOO) # => "No key foo"
342
+ #
343
+ # @param key [Object]
344
+ #
345
+ # @return [Object]
346
+ def default(*key)
347
+ case key.size
348
+ when 0
349
+ @ir.default
350
+ when 1
351
+ @ir.default(transform(key.first))
352
+ else
353
+ @ir.default(*key)
354
+ end
355
+ end
356
+
357
+ # @!method default=(value)
358
+ # Sets the default value to `value`.
359
+ #
360
+ # @example
361
+ # t = Tash.new
362
+ # t.default # => nil
363
+ # t.default = false # => false
364
+ # t.default # => false
365
+ #
366
+ # @param value [Object]
367
+ #
368
+ # @return [Object]
369
+
370
+ # Returns the default proc for `self`.
371
+ #
372
+ # @example
373
+ # t = Tash.new
374
+ # t.default_proc # => nil
375
+ # t.default_proc = proc { |tash, key| "Default value for #{key}" }
376
+ # t.default_proc.class # => Proc
377
+ #
378
+ # @return [Proc or nil]
379
+ def default_proc # rubocop:disable Style/TrivialAccessors (I want it to show as a method in the docs.)
380
+ @default_proc
381
+ end
382
+
383
+ # @overload default_proc=(proc)
384
+ #
385
+ # Sets the default proc for `self` to `proc`.
386
+ #
387
+ # @example
388
+ # t = Tash.new
389
+ # t.default_proc # => nil
390
+ # t.default_proc = proc { |tash, key| "Default value for #{key}" }
391
+ # t.default_proc.class # => Proc
392
+ # t.default_proc = nil
393
+ # t.default_proc # => nil
394
+ #
395
+ # @param proc [Proc] receives self and a transformed key
396
+ #
397
+ # @return [Proc]
398
+ def default_proc=(prok)
399
+ @default_proc = prok
400
+
401
+ @ir.default_proc = proc { |_, k| prok.call(self, transform(k)) }
402
+ end
403
+
404
+ # Deletes the entry for the given transformed `key` and returns its
405
+ # associated value.
406
+ #
407
+ # @example
408
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
409
+ # t.delete(:bar) # => 1
410
+ # t.delete(:bar) # => nil
411
+ # t # => {:foo=>0, :baz=>2}
412
+ #
413
+ # @example With a block and a `key` found.
414
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
415
+ # t.delete(:baz) { |key| raise 'Will never happen'} # => 2
416
+ # t # => {:foo=>0, :bar=>1}
417
+ #
418
+ # @example With a block and no `key` found.
419
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
420
+ # t.delete(:nosuch) { |key| "Key #{key} not found" } # => "Key nosuch not found"
421
+ # t # => {:foo=>0, :bar=>1, :baz=>2}
422
+ #
423
+ # @param key [Object]
424
+ # @param block [Proc] receives a transformed key
425
+ #
426
+ # @return [value or nil, Object]
427
+ def delete(key, &block)
428
+ @ir.delete(transform(key), &block)
429
+ end
430
+
431
+ # If a block given, calls the block with each key-value pair; deletes each
432
+ # entry for which the block returns a truthy value.
433
+ #
434
+ # @example Without block
435
+ # t = Tash[foo: 0, bar: 1, baz: 2]
436
+ # e = t.delete_if # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:delete_if>
437
+ # e.each { |key, value| value > 0 } # => {:foo=>0}
438
+ #
439
+ # @example With block
440
+ # t = Tash[foo: 0, bar: 1, baz: 2]
441
+ # t.delete_if { |key, value| value > 0 } # => {:foo=>0}
442
+ #
443
+ # @param block [Proc] receives a transformed key and value
444
+ #
445
+ # @return [Enumerator, self]
446
+ def delete_if(&block)
447
+ return to_enum(:delete_if) unless block
448
+
449
+ @ir.delete_if(&block)
450
+ self
451
+ end
452
+
453
+ # Finds and returns the object in nested objects that is specified by
454
+ # transformed `key` and `identifiers`. The nested objects may be instances of
455
+ # various classes. This method will use the default values for keys that are
456
+ # not present.
457
+ #
458
+ # @example Nested Tashes
459
+ # t = Tash[Foo: Tash[Bar: 2, &:downcase], &:downcase]
460
+ # t.dig(:foo) # => {:bar=>2}
461
+ # t.dig(:foo, :bar) # => 2
462
+ # t.dig(:foo, :bar, :BAZ) # => nil
463
+ #
464
+ # @example Nested Arrays
465
+ # t = Tash[foo: [:a, :b, :c]]
466
+ # t.dig(:foo, 2) # => :c
467
+ #
468
+ # @example Default values
469
+ # t = Tash[foo: Tash[bar: [:a, :b, :c]]]
470
+ # t.dig(:hello) # => nil
471
+ # t.default_proc = -> (tash, _key) { tash }
472
+ # t.dig(:hello, :world) # => t
473
+ # t.dig(:hello, :world, :foo, :bar, 2) # => :c
474
+ #
475
+ # @param key [Object]
476
+ # @param *identifiers [Object]
477
+ #
478
+ # @return [Object]
479
+ def dig(key, *identifiers)
480
+ @ir.dig(transform(key), *identifiers)
481
+ end
482
+
483
+ # Calls the given block with each key-value pair. Returns a new Enumerator if
484
+ # no block is given.
485
+ #
486
+ # @example Without block
487
+ # t = Tash[foo: 0, bar: 1, baz: 2]
488
+ # t.each # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:each>
489
+ #
490
+ # @example With block
491
+ # t = Tash[foo: 0, bar: 1, baz: 2]
492
+ # t.each {|key, value| puts "#{key}: #{value}"}
493
+ # #=> foo: 0
494
+ # #=> bar: 1
495
+ # #=> baz: 2
496
+ #
497
+ # @param block [Proc] receives a transformed key and the value
498
+ #
499
+ # @return [Enumerator, self]
500
+ def each(&block)
501
+ return to_enum(:each) unless block
502
+
503
+ @ir.each(&block)
504
+ self
505
+ end
506
+ alias each_pair each
507
+
508
+ # Calls the given block with each key.
509
+ #
510
+ # @example Without block
511
+ # t = Tash[foo: 0, bar: 1, baz: 2]
512
+ # t.each_key # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:each_key>
513
+ #
514
+ # @example With block
515
+ # t = Tash[foo: 0, bar: 1, baz: 2]
516
+ # t.each_key {|key| puts key}
517
+ # #=> foo
518
+ # #=> bar
519
+ # #=> baz
520
+ #
521
+ # @param block [Proc] receives a transformed key
522
+ #
523
+ # @return [Enumerator, self]
524
+ def each_key(&block)
525
+ return to_enum(:each_key) unless block
526
+
527
+ @ir.each_key(&block)
528
+ self
529
+ end
530
+
531
+ # Calls the given block with each value.
532
+ #
533
+ # @example Without block
534
+ # t = Tash[foo: 0, bar: 1, baz: 2]
535
+ # t.each_value # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:each_value>
536
+ #
537
+ # @example With block
538
+ # t = Tash[foo: 0, bar: 1, baz: 2]
539
+ # t.each_value {|value| puts value}
540
+ # #=> 0
541
+ # #=> 1
542
+ # #=> 2
543
+ #
544
+ # @param block [Proc] receives a value
545
+ #
546
+ # @return [Enumerator, self]
547
+ def each_value(&block)
548
+ return to_enum(:each_value) unless block
549
+
550
+ @ir.each_value(&block)
551
+ self
552
+ end
553
+
554
+ # @!method empty?
555
+ # Returns `true` if there are no tash entries, `false` otherwise.
556
+ #
557
+ # @example
558
+ # Tash[].empty? # => true
559
+ # Tash[foo: 0, bar: 1, baz: 2].empty? # => false
560
+ #
561
+ # @return [true or false]
562
+
563
+ # Returns `true` if all of the following are true:
564
+ #
565
+ # * object is a Tash object.
566
+ # * tash and object have the same keys (regardless of order).
567
+ # * For each key `key`, `tash[key] eql? other[key]`.
568
+ #
569
+ # Otherwise, returns `false`.
570
+ #
571
+ # @example
572
+ # t1 = Tash[foo: 0, bar: 1, baz: 2]
573
+ # t2 = Tash[foo: 0, bar: 1, baz: 2]
574
+ # t1.eql? t2 # => true
575
+ # h3 = Tash[baz: 2, bar: 1, foo: 0]
576
+ # t1.eql? h3 # => true
577
+ #
578
+ # @param other [Object]
579
+ #
580
+ # @return [true or false]
581
+ def eql?(other)
582
+ return false unless other.is_a?(self.class)
583
+
584
+ @ir.eql?(other.to_hash)
585
+ end
586
+
587
+ # Returns a new Tash excluding entries for the given `keys`. Any given keys
588
+ # that are not found are ignored. The transform proc is copied to the new
589
+ # Tash.
590
+ #
591
+ # @example
592
+ # t = Tash[a: 100, b: 200, c: 300]
593
+ # t.except(:a) #=> {:b=>200, :c=>300}
594
+ #
595
+ # @param *keys [Array<Object>]
596
+ #
597
+ # @return [Tash]
598
+ def except(*keys)
599
+ new_from_self(@ir.except(*keys))
600
+ end if current_ruby_version > '2.7'
601
+
602
+ # @overload fetch(key)
603
+ # @overload fetch(key, default_value)
604
+ #
605
+ # Returns the value for the given `key`, if found. Raises `KeyError` if
606
+ # neither `default_value` nor a block was given.
607
+ #
608
+ # @note This method does not use the values of either `default` or
609
+ # `default_proc`.
610
+ #
611
+ # @example
612
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
613
+ # t.fetch(:bar) # => 1
614
+ #
615
+ # @example With a default
616
+ # Tash.new.fetch(:nosuch, :default) # => :default
617
+ #
618
+ # @example With a default block
619
+ # Tash.new.fetch(:NOSUCH) {|key| "No key #{key}"} # => "No key nosuch"
620
+ #
621
+ # @param key [Object]
622
+ # @param default_value [Object]
623
+ # @param block [Proc] receives a transformed `key`
624
+ #
625
+ # @return [Object]
626
+ #
627
+ # @raise [KeyError] When `key` is not found and no default is provided.
628
+ def fetch(key, *default_value, &block)
629
+ if block
630
+ @ir.fetch(transform(key), &block)
631
+ else
632
+ @ir.fetch(transform(key), *default_value)
633
+ end
634
+ end
635
+
636
+ # Returns a new Array containing the values associated with the given keys
637
+ # *keys. When a block is given, calls the block with each missing transformed
638
+ # key, treating the block's return value as the value for that key.
639
+ #
640
+ # @example Without a block
641
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
642
+ # t.fetch_values(:baz, :foo) # => [2, 0]
643
+ #
644
+ # @example With a block
645
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
646
+ # values = t.fetch_values(:bar, :foo, :bad, :bam) {|key| key.to_s}
647
+ # values # => [1, 0, "bad", "bam"]
648
+ #
649
+ # @param *keys [Array<Object>]
650
+ # @param block [Proc] receives a transformed `key`
651
+ #
652
+ # @return [Array]
653
+ def fetch_values(*keys, &block)
654
+ @ir.fetch_values(*keys.map { |k| transform(k) }, &block)
655
+ end
656
+
657
+ # @!method flatten
658
+ # @overload flatten
659
+ # @overload flatten(level)
660
+ #
661
+ # Returns a new Array object that is a 1-dimensional flattening of `self`.
662
+ #
663
+ # @example
664
+ # t = Tash[foo: 0, bar: [:bat, 3], baz: 2]
665
+ # t.flatten # => [:foo, 0, :bar, [:bat, 3], :baz, 2]
666
+ #
667
+ # @example level > 1
668
+ # t = Tash[foo: 0, bar: [:bat, [:baz, [:bat, ]]]]
669
+ # t.flatten(1) # => [:foo, 0, :bar, [:bat, [:baz, [:bat]]]]
670
+ # t.flatten(2) # => [:foo, 0, :bar, :bat, [:baz, [:bat]]]
671
+ # t.flatten(3) # => [:foo, 0, :bar, :bat, :baz, [:bat]]
672
+ # t.flatten(4) # => [:foo, 0, :bar, :bat, :baz, :bat]
673
+ #
674
+ # @example negative levels flatten everything
675
+ # t = Tash[foo: 0, bar: [:bat, [:baz, [:bat, ]]]]
676
+ # t.flatten(-1) # => [:foo, 0, :bar, :bat, :baz, :bat]
677
+ # t.flatten(-2) # => [:foo, 0, :bar, :bat, :baz, :bat]
678
+ #
679
+ # @example level == 0 is the same as to_a
680
+ # t = Tash[foo: 0, bar: [:bat, 3], baz: 2]
681
+ # t.flatten(0) # => [[:foo, 0], [:bar, [:bat, 3]], [:baz, 2]]
682
+ # t.flatten(0) == t.to_a # => true
683
+ #
684
+ # @param level [Integer]
685
+ #
686
+ # @return [Array]
687
+
688
+ # @!method hash
689
+ # Returns the Integer hash-code for the hash. Two Hash objects have the
690
+ # same hash-code if their content is the same (regardless or order).
691
+ #
692
+ # @example
693
+ # t1 = Tash[foo: 0, bar: 1, baz: 2]
694
+ # t2 = Tash[baz: 2, bar: 1, foo: 0]
695
+ # t2.hash == t1.hash # => true
696
+ # t2.eql? h1 # => true
697
+ #
698
+ # @return [Integer]
699
+
700
+ # @!method inspect
701
+ # Returns a new String containing the tash entries.
702
+ #
703
+ # @example
704
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
705
+ # t.inspect # => "{:foo=>0, :bar=>1, :baz=>2}"
706
+ #
707
+ # @return [String]
708
+
709
+ # Returns a new Tash object with the each key-value pair inverted. The values
710
+ # will be processed using the key transformation.
711
+ #
712
+ # @example
713
+ # t = Tash[foo: 'Foo', bar: 'Bar', baz: 'Baz', &:downcase]
714
+ # t1 = t.invert
715
+ # t1 # => {'foo'=>:foo, 'bar'=>:bar, 'baz'=>:baz}
716
+ #
717
+ # @return [Tash]
718
+ def invert
719
+ new_ir = @ir.invert
720
+ new_ir.transform_keys! { |k| transform(k) }
721
+
722
+ new_from_self(new_ir)
723
+ end
724
+
725
+ # Calls the block for each key-value pair; retains the entry if the block
726
+ # returns a truthy value; otherwise deletes the entry.
727
+ #
728
+ # @example Without block
729
+ # t = Tash[foo: 0, bar: 1, baz: 2]
730
+ # e = t.keep_if # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:keep_if>
731
+ # e.each { |key, value| key.start_with?('b') } # => {:bar=>1, :baz=>2}
732
+ #
733
+ # @example With block
734
+ # t = Tash[foo: 0, bar: 1, baz: 2]
735
+ # t.keep_if { |key, value| key.start_with?('b') } # => {:bar=>1, :baz=>2}
736
+ #
737
+ # @param block [Proc] receives a transformed key and value
738
+ #
739
+ # @return [Enumerator, self]
740
+ def keep_if(&block)
741
+ return to_enum(:keep_if) unless block
742
+
743
+ @ir.keep_if(&block)
744
+ self
745
+ end
746
+
747
+ # @!method key(value)
748
+ # Returns the transformed key for the first-found entry with the given
749
+ # `value`. Returns `nil` if the key is not found.
750
+ #
751
+ # @example
752
+ # t = Tash[foo: 0, bar: 2, baz: 2]
753
+ # t.key(0) # => :foo
754
+ # t.key(2) # => :bar
755
+ #
756
+ # @param value [Object]
757
+ #
758
+ # @return [key or nil]
759
+
760
+ # Returns `true` if `key` after transformation is a key in `self`, otherwise
761
+ # `false`.
762
+ #
763
+ # @example
764
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
765
+ # t.key?(:FOO) # => true
766
+ # t.key?(:bat) # => false
767
+ #
768
+ # @param key [Object]
769
+ #
770
+ # @return [true or false]
771
+ def key?(key)
772
+ @ir.key?(transform(key))
773
+ end
774
+ alias has_key? key?
775
+ alias include? key?
776
+ alias member? key?
777
+
778
+ # @!method keys
779
+ # Returns a new Array containing all keys in `self`.
780
+ #
781
+ # @example
782
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
783
+ # t.keys # => [:foo, :bar, :baz]
784
+ #
785
+ # @return [Array]
786
+
787
+ # Returns the new Tash formed by merging each of `other_tashes_or_hashes`
788
+ # into a copy of `self`.
789
+ #
790
+ # Each argument in `other_tashes_or_hashes` must be a Tash or Hash.
791
+ #
792
+ # With arguments and no block:
793
+ #
794
+ # * Returns the new Tash object formed by merging each successive item in
795
+ # other_tashes_or_hashes into self.
796
+ # * Each new-key entry is added at the end.
797
+ # * Each duplicate-key entry's value overwrites the previous value.
798
+ #
799
+ # With arguments and a block:
800
+ #
801
+ # * Returns a new Tash object that is the merge of self and each given
802
+ # tash or hash.
803
+ # * The given tashes or hashes are merged left to right.
804
+ # * Each new-key entry is added at the end.
805
+ # * For each duplicate key:
806
+ # * Calls the block with the transformed key and the old and new values.
807
+ # * The block's return value becomes the new value for the entry.
808
+ #
809
+ # With no arguments:
810
+ #
811
+ # * Returns a copy of self.
812
+ # * The block, if given, is ignored.
813
+ #
814
+ # @example With arguments and no block
815
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
816
+ # t1 = Tash[bat: 3, bar: 4]
817
+ # h = {BAM: 5, BAT: 6}
818
+ # t.merge(t1, h) # => {:foo=>0, :bar=>4, :baz=>2, :bat=>6, :bam=>5}
819
+ #
820
+ # @example With arguments and a block
821
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
822
+ # t1 = Tash[bat: 3, bar: 4]
823
+ # h = {BAM: 5, BAT: 6}
824
+ # t2 = t.merge(t1, h) { |key, old_value, new_value| old_value + new_value }
825
+ # t2 # => {:foo=>0, :bar=>5, :baz=>2, :bat=>9, :bam=>5}
826
+ #
827
+ # @example With no arguments
828
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
829
+ # t.merge # => {:foo=>0, :bar=>1, :baz=>2}
830
+ # t1 = t.merge { |key, old_value, new_value| raise 'Cannot happen' }
831
+ # t1 # => {:foo=>0, :bar=>1, :baz=>2}
832
+ #
833
+ # @param *others [Tash or Hash]
834
+ # @param block [Proc] receives a transformed key, the old value, and the new
835
+ # value
836
+ #
837
+ # @return [Tash]
838
+ def merge(*others, &block)
839
+ new_ir = others.each_with_object(@ir.dup) do |other, ir|
840
+ ir.merge!(other.to_hash.transform_keys { |k| transform(k) }, &block)
841
+ end
842
+ new_from_self(new_ir)
843
+ end
844
+
845
+ # Merges each of `other_tashes_or_hashes` into `self`.
846
+ #
847
+ # Each argument in `other_tashes_or_hashes` must be a Tash or Hash.
848
+ #
849
+ # With arguments and no block:
850
+ #
851
+ # * Returns `self`, after the given tashes and hashes are merged into it.
852
+ # * The given tashes and hashes are merged left to right.
853
+ # * Each new entry is added at the end.
854
+ # * Each duplicate-key entry's value overwrites the previous value.
855
+ #
856
+ # With arguments and a block:
857
+ #
858
+ # * Returns `self`, after the given tashes and hashes are merged.
859
+ # * The given tashes and hashes are merged left to right.
860
+ # * Each new-key entry is added at the end.
861
+ # * For each duplicate key:
862
+ # * Calls the block with the transformed key and the old and new values.
863
+ # * The block's return value becomes the new value for the entry.
864
+ #
865
+ # With no arguments:
866
+ #
867
+ # * Returns `self`, unmodified.
868
+ # * The block, if given, is ignored.
869
+ #
870
+ # @example With arguments and no block
871
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
872
+ # t1 = Tash[bat: 3, bar: 4]
873
+ # h = {BAM: 5, BAT: 6}
874
+ # t.merge!(t1, h) # => {:foo=>0, :bar=>4, :baz=>2, :bat=>6, :bam=>5}
875
+ #
876
+ # @example With arguments and a block
877
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
878
+ # t1 = Tash[bat: 3, bar: 4]
879
+ # h = {BAM: 5, BAT: 6}
880
+ # t2 = t.merge!(t1, h) { |key, old_value, new_value| old_value + new_value }
881
+ # t2 # => {:foo=>0, :bar=>5, :baz=>2, :bat=>9, :bam=>5}
882
+ #
883
+ # @example With no arguments
884
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
885
+ # t.merge # => {:foo=>0, :bar=>1, :baz=>2}
886
+ # t1 = t.merge! { |key, old_value, new_value| raise 'Cannot happen' }
887
+ # t1 # => {:foo=>0, :bar=>1, :baz=>2}
888
+ #
889
+ # @param *others [Tash or Hash]
890
+ # @param block [Proc] receives a transformed key, the old value, and the new
891
+ # value
892
+ #
893
+ # @return [self]
894
+ def merge!(*others, &block)
895
+ others.each do |other|
896
+ @ir.merge!(other.to_hash.transform_keys { |k| transform(k) }, &block)
897
+ end
898
+ self
899
+ end
900
+ alias update merge!
901
+
902
+ # @!method rassoc(value)
903
+ # Returns a new 2-element Array consisting of the key and value of the
904
+ # first-found entry whose value is `==` to value. Returns `nil` if no such
905
+ # value found.
906
+ #
907
+ # @example
908
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
909
+ # t.rassoc(1) # => [:bar, 1]
910
+ #
911
+ # @param value [Object]
912
+ #
913
+ # @return [Array<K,V> or nil]
914
+
915
+ # @!method rehash
916
+ # Rebuilds the hash table by recomputing the hash index for each key. The
917
+ # hash table becomes invalid if the hash value of a key has changed after
918
+ # the entry was created.
919
+ #
920
+ # @return [self]
921
+
922
+ # Returns a new Tash object whose entries are all those from `self` for which
923
+ # the block returns `false` or `nil`. Returns a new Enumerator if no block
924
+ # given.
925
+ #
926
+ # @example Without a block
927
+ # t = Tash[foo: 0, bar: 1, baz: 2]
928
+ # e = t.reject # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:reject>
929
+ # t1 = e.each { |key, value| key.start_with?('b') }
930
+ # t1 # => {:foo=>0}
931
+ #
932
+ # @example With a block
933
+ # t = Tash[foo: 0, bar: 1, baz: 2]
934
+ # t1 = t.reject { |key, value| key.start_with?('b') }
935
+ # t1 # => {:foo=>0}
936
+ #
937
+ # @param block [Proc] receives a transformed key and value
938
+ #
939
+ # @return [Enumerator, Tash]
940
+ def reject(&block)
941
+ return to_enum(:reject) unless block
942
+
943
+ new_from_self(@ir.reject(&block))
944
+ end
945
+
946
+ # Returns `self`, whose remaining entries are those for which the block
947
+ # returns `false` or `nil`. Returns `nil` if no entries are removed.
948
+ #
949
+ # @example Without a block
950
+ # t = Tash[foo: 0, bar: 1, baz: 2]
951
+ # e = t.reject! # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:reject!>
952
+ # t1 = e.each { |key, value| key.start_with?('b') } # => {:foo=>0}
953
+ #
954
+ # @example With a block
955
+ # t = Tash[foo: 0, bar: 1, baz: 2]
956
+ # t.reject! { |key, value| value < 2 } # => {:foo=>0, :bar=>1}
957
+ #
958
+ # @param block [Proc] receives a transformed key and value
959
+ #
960
+ # @return [Enumerator, self or nil]
961
+ def reject!(&block)
962
+ return to_enum(:reject!) unless block
963
+
964
+ self if @ir.reject!(&block)
965
+ end
966
+
967
+ # Replaces the entire contents of `self` with the contents of `other`. If
968
+ # `other` is a tash it also replaces the transform block.
969
+ #
970
+ # @example With a hash
971
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
972
+ # t.replace({Bat: 3, Bam: 4}) # => {:bat=>3, :bam=>4}
973
+ #
974
+ # @example With a tash
975
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
976
+ # t.replace(Tash[Bat: 3, Bam: 4, &:upcase]) # => {:BAT=>3, :BAM=>4}
977
+ #
978
+ # @param other [Tash, Hash]
979
+ #
980
+ # @return [self]
981
+ def replace(other)
982
+ if other.is_a?(self.class)
983
+ @ir.replace(other.to_hash)
984
+ @transform = other.transform_proc
985
+ else
986
+ @ir.replace(other.to_hash.transform_keys { |k| transform(k) })
987
+ end
988
+
989
+ self
990
+ end
991
+
992
+ # Returns a new Tash object whose entries are those for which the block
993
+ # returns a truthy value. Returns a new Enumerator if no block given.
994
+ #
995
+ # @example Without a block
996
+ # t = Tash[foo: 0, bar: 1, baz: 2]
997
+ # e = t.select # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:select>
998
+ # e.each { |key, value| value < 2 } # => {:foo=>0, :bar=>1}
999
+ #
1000
+ # @example With a block
1001
+ # t = Tash[foo: 0, bar: 1, baz: 2]
1002
+ # t.select { |key, value| value < 2 } # => {:foo=>0, :bar=>1}
1003
+ #
1004
+ # @param block [Proc] receives a transformed key and value
1005
+ #
1006
+ # @return [Enumerator, Tash]
1007
+ def select(&block)
1008
+ return to_enum(:select) unless block
1009
+
1010
+ new_from_self(@ir.select(&block))
1011
+ end
1012
+ alias filter select
1013
+
1014
+ # Returns `self`, whose entries are those for which the block returns a truthy
1015
+ # value. When given a block, it returns `nil` if no entries are removed.
1016
+ #
1017
+ # @example Without a block
1018
+ # t = Tash[foo: 0, bar: 1, baz: 2]
1019
+ # e = t.select! # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:select!>
1020
+ # e.each { |key, value| value < 2 } # => {:foo=>0, :bar=>1}
1021
+ #
1022
+ # @example With a block
1023
+ # t = Tash[foo: 0, bar: 1, baz: 2]
1024
+ # t.select! { |key, value| value < 2 } # => {:foo=>0, :bar=>1}
1025
+ #
1026
+ # @param block [Proc] receives a transformed key and value
1027
+ #
1028
+ # @return [Enumerator, self or nil]
1029
+ def select!(&block)
1030
+ return to_enum(:select!) unless block
1031
+
1032
+ self if @ir.select!(&block)
1033
+ end
1034
+ alias filter! select!
1035
+
1036
+ # @!method shift
1037
+ # Removes the first tash entry and returns a 2-element Array containing the
1038
+ # removed key and value. Returns the default value if the hash is empty.
1039
+ #
1040
+ # @example
1041
+ # t = Tash[foo: 0, bar: 1, baz: 2]
1042
+ # t.shift # => [:foo, 0]
1043
+ # t # => {:bar=>1, :baz=>2}
1044
+ #
1045
+ # @return [[key, value] or default value]
1046
+
1047
+ # @!method size
1048
+ # Returns the count of entries in `self`.
1049
+ #
1050
+ # @example
1051
+ # Tash[foo: 0, bar: 1, baz: 2].size # => 3
1052
+ #
1053
+ # @return [Integer]
1054
+
1055
+ # Returns a new Tash object containing the entries for the given transformed
1056
+ # `keys`. Any given `keys` that are not found are ignored.
1057
+ #
1058
+ # @example
1059
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1060
+ # t.slice(:BAZ, :foo) # => {:baz=>2, :foo=>0}
1061
+ #
1062
+ # @param *keys [Array<Object>]
1063
+ #
1064
+ # @return [Tash]
1065
+ def slice(*keys)
1066
+ new_from_self(@ir.slice(*keys.map { |k| transform(k) }))
1067
+ end
1068
+
1069
+ # @!method to_a
1070
+ # Returns a new Array of 2-element Array objects; each nested Array
1071
+ # contains a key-value pair from `self`.
1072
+ #
1073
+ # @example
1074
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1075
+ # t.to_a # => [[:foo, 0], [:bar, 1], [:baz, 2]]
1076
+ #
1077
+ # @return [Array]
1078
+
1079
+ # Returns a Hash containing the content of `self`. When a block is given,
1080
+ # returns a Hash object whose content is based on the block; the block should
1081
+ # return a 2-element Array object specifying the key-value pair to be
1082
+ # included in the returned Array.
1083
+ #
1084
+ # @example
1085
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1086
+ # h1 = t.to_h { |key, value| [value, key] }
1087
+ # h1 # => {0=>:foo, 1=>:bar, 2=>:baz}
1088
+ #
1089
+ # @param block [Proc]
1090
+ #
1091
+ # @return [Hash]
1092
+ def to_h(&block)
1093
+ if block
1094
+ @ir.to_h(&block)
1095
+ else
1096
+ to_hash
1097
+ end
1098
+ end
1099
+
1100
+ # Returns tash as a Hash.
1101
+ #
1102
+ # @return [Hash]
1103
+ def to_hash
1104
+ @ir.dup
1105
+ end
1106
+
1107
+ # Returns a Proc object that maps a key to its value.
1108
+ #
1109
+ # @example
1110
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1111
+ # proc = t.to_proc
1112
+ # proc.class # => Proc
1113
+ # proc.call(:foo) # => 0
1114
+ # proc.call(:BAR) # => 1
1115
+ # proc.call(:nosuch) # => nil
1116
+ #
1117
+ # @return [Proc]
1118
+ def to_proc
1119
+ p = @ir.to_proc
1120
+ lambda do |key|
1121
+ p.call(transform(key))
1122
+ end
1123
+ end
1124
+
1125
+ # Returns the transform proc for `self`.
1126
+ #
1127
+ # @example
1128
+ # t = Tash.new
1129
+ # t.transform_proc # => nil
1130
+ # t = Tash.new(&:to_s)
1131
+ # t.transform_proc.class # => Proc
1132
+ # t.transform_proc.call(:a) # => "a"
1133
+ #
1134
+ # @return [Proc or nil]
1135
+ def transform_proc
1136
+ @transform
1137
+ end
1138
+
1139
+ # Returns a new Tash object; each entry has:
1140
+ #
1141
+ # * A key from `self`.
1142
+ # * A value provided by the block.
1143
+ #
1144
+ # @example Without block
1145
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1146
+ # e = t.transform_values # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:transform_values>
1147
+ # t1 = e.each { |value| value * 100 }
1148
+ # t1 # => {:foo=>0, :bar=>100, :baz=>200}
1149
+ #
1150
+ # @example With a block
1151
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1152
+ # t1 = t.transform_values { |value| value * 100 }
1153
+ # t1 # => {:foo=>0, :bar=>100, :baz=>200}
1154
+ #
1155
+ # @return [Tash]
1156
+ def transform_values(&block)
1157
+ return to_enum(:transform_values) unless block
1158
+
1159
+ new_from_self(@ir.transform_values(&block))
1160
+ end
1161
+
1162
+ # Returns `self`, whose keys are unchanged, and whose values are determined by
1163
+ # the given block.
1164
+ #
1165
+ # @example Without a block
1166
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1167
+ # e = t.transform_values! # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:transform_values!>
1168
+ # t1 = e.each { |value| value * 100 }
1169
+ # t1 # => {:foo=>0, :bar=>100, :baz=>200}
1170
+ #
1171
+ # @example With a block
1172
+ # t = Tash[Foo: 0, Bar: 1, Baz: 2, &:downcase]
1173
+ # t.transform_values! { |value| value * 100 } # => {:foo=>0, :bar=>100, :baz=>200}
1174
+ #
1175
+ # @return [self]
1176
+ def transform_values!(&block)
1177
+ return to_enum(:transform_values!) unless block
1178
+
1179
+ self if @ir.transform_values!(&block)
1180
+ end
1181
+
1182
+ # @!method value?
1183
+ # Returns `true` if `value` is a value in `self`, otherwise `false`.
1184
+ #
1185
+ # @return [Boolean]
1186
+
1187
+ # @!method values
1188
+ # Returns a new Array containing all values in `self`.
1189
+ #
1190
+ # @example
1191
+ # t = Tash[foo: 0, bar: 1, baz: 2]
1192
+ # t.values # => [0, 1, 2]
1193
+ #
1194
+ # @return [Array]
1195
+
1196
+ # Returns a new Array containing values for the given transformed `keys`. The
1197
+ # default values are returned for any keys that are not found.
1198
+ #
1199
+ # @example
1200
+ # t = Tash[foo: 0, bar: 1, baz: 2]
1201
+ # t.values_at(:baz, :FOO) # => [2, 0]
1202
+ #
1203
+ # @example keys not found
1204
+ # t.values_at(:hello, :foo) # => [nil, 0]
1205
+ #
1206
+ # @return [Array]
1207
+ def values_at(*keys)
1208
+ @ir.values_at(*keys.map { |k| transform(k) })
1209
+ end
1210
+
1211
+ protected
1212
+
1213
+ attr_writer :ir
1214
+
1215
+ private
1216
+
1217
+ def transform(key)
1218
+ return key unless @transform
1219
+
1220
+ @transform.call(key)
1221
+ end
1222
+
1223
+ def new_from_self(new_ir)
1224
+ self.class.new(&@transform).tap { |tash| tash.ir = new_ir }
1225
+ end
1226
+
1227
+ def current_ruby_version
1228
+ self.class.send(:current_ruby_version)
1229
+ end
1230
+ end