tash 1.0.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.
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