Class: Map

Inherits:
Hash
  • Object
show all
Defined in:
lib/map.rb,
lib/map/struct.rb,
lib/map/options.rb

Defined Under Namespace

Modules: Arguments, Options Classes: Struct

Constant Summary collapse

Version =
'2.4.1'
Load =
Kernel.method(:load)
Dynamic =
lambda do
  conversion_methods.reverse_each do |method|
    add_conversion_method!(method)
  end
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, &block) ⇒ Map

Returns a new instance of Map.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/map.rb', line 164

def initialize(*args, &block)
  case args.size
    when 0
      super(&block)

    when 1
      case args.first
        when Hash
          initialize_from_hash(args.first)
        when Array
          initialize_from_array(args.first)
        else
          initialize_from_hash(args.first.to_hash)
      end

    else
      initialize_from_array(args)
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(*args, &block) ⇒ Object

a sane method missing that only supports writing values or reading *previously set* values



545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/map.rb', line 545

def method_missing(*args, &block)
  method = args.first.to_s
  case method
    when /=$/
      key = args.shift.to_s.chomp('=')
      value = args.shift
      self[key] = value
    else
      key = method
      super(*args, &block) unless has_key?(key)
      self[key]
  end
end

Class Method Details

.add_conversion_method!(method) ⇒ Object

Raises:

  • (ArguementError)


66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/map.rb', line 66

def add_conversion_method!(method)
  method = method.to_s.strip
  raise ArguementError if method.empty?
  module_eval(<<-__, __FILE__, __LINE__)
    unless public_method_defined?(#{ method.inspect })
      def #{ method }
        self
      end
    end
    unless conversion_methods.include?(#{ method.inspect })
      conversion_methods.unshift(#{ method.inspect })
    end
  __
end

.allocateObject



28
29
30
31
32
33
# File 'lib/map.rb', line 28

def allocate
  super.instance_eval do
    @keys = []
    self
  end
end

.alphanumeric_key_for(key) ⇒ Object



653
654
655
656
# File 'lib/map.rb', line 653

def Map.alphanumeric_key_for(key)
  return key if Numeric===key
  key.to_s =~ %r/^\d+$/ ? Integer(key) : key
end

.coerce(other) ⇒ Object



49
50
51
52
# File 'lib/map.rb', line 49

def coerce(other)
  return other if other.class == self
  allocate.update(other.to_hash)
end

.conversion_methodsObject



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/map.rb', line 54

def conversion_methods
  @conversion_methods ||= (
    map_like = ancestors.select{|ancestor| ancestor <= Map}
    type_names = map_like.map do |ancestor|
      name = ancestor.name.to_s.strip
      next if name.empty?
      name.downcase.gsub(/::/, '_')
    end.compact
    type_names.map{|type_name| "to_#{ type_name }"}
  )
end

.convert_key(key) ⇒ Object



210
211
212
# File 'lib/map.rb', line 210

def self.convert_key(key)
  key.kind_of?(Symbol) ? key.to_s : key
end

.convert_value(value) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/map.rb', line 221

def self.convert_value(value)
  conversion_methods.each do |method|
    return value.send(method) if value.respond_to?(method)
  end

  case value
    when Hash
      coerce(value)
    when Array
      value.map{|v| convert_value(v)}
    else
      value
  end
end

.depth_first_each(enumerable, path = [], accum = [], &block) ⇒ Object



662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# File 'lib/map.rb', line 662

def Map.depth_first_each(enumerable, path = [], accum = [], &block)
  Map.pairs_for(enumerable) do |key, val|
    path.push(key)
    if((val.is_a?(Hash) or val.is_a?(Array)) and not val.empty?)
      Map.depth_first_each(val, path, accum)
    else
      accum << [path.dup, val]
    end
    path.pop()
  end
  if block
    accum.each{|keys, val| block.call(keys, val)}
  else
    [path, accum]
  end
end

.each_pair(*args, &block) ⇒ Object

iterate over arguments in pairs smartly.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/map.rb', line 88

def each_pair(*args, &block)
  size = args.size
  parity = size % 2 == 0 ? :even : :odd
  first = args.first

  if block.nil?
    result = []
    block = lambda{|*kv| result.push(kv)}
  else
    result = args
  end

  return args if size == 0

  if size == 1
    conversion_methods.each do |method|
      if first.respond_to?(method)
        first = first.send(method)
        break
      end
    end

    if first.respond_to?(:each_pair)
      first.each_pair do |key, val|
        block.call(key, val)
      end
      return args
    end

    if first.respond_to?(:each_slice)
      first.each_slice(2) do |key, val|
        block.call(key, val)
      end
      return args
    end

    raise(ArgumentError, 'odd number of arguments for Map')
  end

  array_of_pairs = args.all?{|a| a.is_a?(Array) and a.size == 2}

  if array_of_pairs
    args.each do |pair|
      key, val, *ignored = pair
      block.call(key, val)
    end
  else
    0.step(args.size - 1, 2) do |a|
      key = args[a]
      val = args[a + 1]
      block.call(key, val)
    end
  end

  args
end

.for(*args, &block) ⇒ Object



42
43
44
45
46
47
# File 'lib/map.rb', line 42

def for(*args, &block)
  if(args.size == 1 and block.nil?)
    return args.first if args.first.class == self
  end
  new(*args, &block)
end

.inherited(other) ⇒ Object



81
82
83
84
# File 'lib/map.rb', line 81

def inherited(other)
  other.module_eval(&Dynamic)
  super
end

.libdir(*args, &block) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/map.rb', line 10

def libdir(*args, &block)
  @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
  libdir = args.empty? ? @libdir : File.join(@libdir, *args.map{|arg| arg.to_s})
ensure
  if block
    begin
      $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.first==libdir
      module_eval(&block)
    ensure
      $LOAD_PATH.shift() if $LOAD_PATH.first==libdir
    end
  end
end

.load(*args, &block) ⇒ Object



24
25
26
# File 'lib/map.rb', line 24

def load(*args, &block)
  libdir{ Load.call(*args, &block) }
end

.map_for(hash) ⇒ Object



201
202
203
204
205
# File 'lib/map.rb', line 201

def Map.map_for(hash)
  map = klass.coerce(hash)
  map.default = hash.default
  map
end

.new(*args, &block) ⇒ Object Also known as: []



35
36
37
38
39
40
# File 'lib/map.rb', line 35

def new(*args, &block)
  allocate.instance_eval do
    initialize(*args, &block)
    self
  end
end

.options_for(*args, &block) ⇒ Object



145
146
147
# File 'lib/map/options.rb', line 145

def Map.options_for(*args, &block)
  Map::Options.for(*args, &block)
end

.options_for!(*args, &block) ⇒ Object



149
150
151
# File 'lib/map/options.rb', line 149

def Map.options_for!(*args, &block)
  Map::Options.for(*args, &block).pop
end

.pairs_for(enumerable, *args, &block) ⇒ Object



679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/map.rb', line 679

def Map.pairs_for(enumerable, *args, &block)
  if block.nil?
    pairs, block = [], lambda{|*pair| pairs.push(pair)}
  else
    pairs = false
  end

  result =
    case enumerable
      when Hash
        enumerable.each_pair(*args, &block)
      when Array
        enumerable.each_with_index(*args) do |val, key|
          block.call(key, val)
        end
      else
        enumerable.each_pair(*args, &block)
    end

  pairs ? pairs : result
end

.struct(*args, &block) ⇒ Object



45
46
47
# File 'lib/map/struct.rb', line 45

def Map.struct(*args, &block)
  new(*args, &block).struct
end

.versionObject



6
7
8
# File 'lib/map.rb', line 6

def version
  Map::Version
end

Instance Method Details

#<=>(other) ⇒ Object



438
439
440
# File 'lib/map.rb', line 438

def <=>(other)
  keys <=> klass.coerce(other).keys
end

#==(hash) ⇒ Object

misc



432
433
434
435
436
# File 'lib/map.rb', line 432

def ==(hash)
  return false unless(Map === hash)
  return false if keys != hash.keys
  super hash
end

#=~(hash) ⇒ Object



442
443
444
# File 'lib/map.rb', line 442

def =~(hash)
  to_hash == klass.coerce(hash).to_hash
end

#[](key) ⇒ Object



286
287
288
# File 'lib/map.rb', line 286

def [](key)
  __get__(convert_key(key))
end

#[]=(key, val) ⇒ Object Also known as: store



279
280
281
282
283
# File 'lib/map.rb', line 279

def []=(key, val)
  key, val = convert(key, val)
  keys.push(key) unless has_key?(key)
  __set__(key, val)
end

#__get__Object



276
# File 'lib/map.rb', line 276

alias_method '__get__', '[]'

#__set__Object

writer/reader methods



275
# File 'lib/map.rb', line 275

alias_method '__set__', '[]='

#__update__Object



277
# File 'lib/map.rb', line 277

alias_method '__update__', 'update'

#alphanumeric_key_for(key) ⇒ Object



658
659
660
# File 'lib/map.rb', line 658

def alphanumeric_key_for(key)
  Map.alphanumeric_key_for(key)
end

#apply(other) ⇒ Object



646
647
648
649
650
651
# File 'lib/map.rb', line 646

def apply(other)
  Map.for(other).depth_first_each do |keys, value|
    set(keys => value) unless !get(keys).nil?
  end
  self
end

#as_hashObject



497
498
499
500
501
502
# File 'lib/map.rb', line 497

def as_hash
  @class = Hash
  yield
ensure
  @class = nil
end

#classObject



504
505
506
# File 'lib/map.rb', line 504

def class
  @class || super
end

#clearObject



371
372
373
374
# File 'lib/map.rb', line 371

def clear
  keys.clear
  super
end

#cloneObject



265
266
267
# File 'lib/map.rb', line 265

def clone
  copy
end

#collection_has_key?(collection, key) ⇒ Boolean

Returns:

  • (Boolean)


593
594
595
596
597
598
599
600
601
# File 'lib/map.rb', line 593

def collection_has_key?(collection, key)
  case collection
    when Hash
      collection.has_key?(key)
    when Array
      return false unless key
      (0...collection.size).include?(Integer(key))
  end
end

#conversion_methodsObject

conversions



476
477
478
# File 'lib/map.rb', line 476

def conversion_methods
  self.class.conversion_methods
end

#convert(key, val) ⇒ Object



244
245
246
# File 'lib/map.rb', line 244

def convert(key, val)
  [convert_key(key), convert_value(val)]
end

#convert_key(key) ⇒ Object



213
214
215
216
217
218
219
# File 'lib/map.rb', line 213

def convert_key(key)
  if klass.respond_to?(:convert_key)
    klass.convert_key(key)
  else
    Map.convert_key(key)
  end
end

#convert_value(value) ⇒ Object Also known as: convert_val



235
236
237
238
239
240
241
# File 'lib/map.rb', line 235

def convert_value(value)
  if klass.respond_to?(:convert_value)
    klass.convert_value(value)
  else
    Map.convert_value(value)
  end
end

#copyObject

maps are aggressive with copy operations. they are all deep copies. make a new one if you really want a shallow copy



251
252
253
254
255
256
257
258
259
# File 'lib/map.rb', line 251

def copy
  default = self.default
  self.default = nil
  copy = Marshal.load(Marshal.dump(self))
  copy.default = default
  copy
ensure
  self.default = default
end

#default(key = nil) ⇒ Object



269
270
271
# File 'lib/map.rb', line 269

def default(key = nil)
  key.is_a?(Symbol) && include?(key = key.to_s) ? self[key] : super
end

#delete(key) ⇒ Object

mutators



365
366
367
368
369
# File 'lib/map.rb', line 365

def delete(key)
  key = convert_key(key)
  keys.delete(key)
  super(key)
end

#delete_ifObject



376
377
378
379
380
381
# File 'lib/map.rb', line 376

def delete_if
  to_delete = []
  keys.each{|key| to_delete.push(key) if yield(key)}
  to_delete.each{|key| delete(key)}
  self
end

#depth_first_each(*args, &block) ⇒ Object



701
702
703
# File 'lib/map.rb', line 701

def depth_first_each(*args, &block)
  Map.depth_first_each(enumerable=self, *args, &block)
end

#dupObject



261
262
263
# File 'lib/map.rb', line 261

def dup
  copy
end

#eachObject Also known as: each_pair



357
358
359
360
# File 'lib/map.rb', line 357

def each
  keys.each{|key| yield(key, self[key])}
  self
end

#each_keyObject



347
348
349
350
# File 'lib/map.rb', line 347

def each_key
  keys.each{|key| yield(key)}
  self
end

#each_valueObject



352
353
354
355
# File 'lib/map.rb', line 352

def each_value
  keys.each{|key| yield self[key]}
  self
end

#each_with_indexObject

iterator methods



342
343
344
345
# File 'lib/map.rb', line 342

def each_with_index
  keys.each_with_index{|key, index| yield([key, self[key]], index)}
  self
end

#fetch(key, *args, &block) ⇒ Object



290
291
292
# File 'lib/map.rb', line 290

def fetch(key, *args, &block)
  super(convert_key(key), *args, &block)
end

#firstObject



332
333
334
# File 'lib/map.rb', line 332

def first
  [keys.first, self[keys.first]]
end

#get(*keys) ⇒ Object

support for compound key indexing and depth first iteration



566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/map.rb', line 566

def get(*keys)
  keys = keys.flatten
  return self[keys.first] if keys.size <= 1
  keys, key = keys[0..-2], keys[-1]
  collection = self
  keys.each do |k|
    k = alphanumeric_key_for(k)
    collection = collection[k]
    return collection unless collection.respond_to?('[]')
  end
  collection[alphanumeric_key_for(key)]
end

#has?(*keys) ⇒ Boolean

Returns:

  • (Boolean)


579
580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/map.rb', line 579

def has?(*keys)
  keys = keys.flatten
  collection = self
  return collection_has_key?(collection, keys.first) if keys.size <= 1
  keys, key = keys[0..-2], keys[-1]
  keys.each do |k|
    k = alphanumeric_key_for(k)
    collection = collection[k]
    return collection unless collection.respond_to?('[]')
  end
  return false unless(collection.is_a?(Hash) or collection.is_a?(Array))
  collection_has_key?(collection, alphanumeric_key_for(key))
end

#idObject

Raises:

  • (NoMethodError)


559
560
561
562
# File 'lib/map.rb', line 559

def id
  raise NoMethodError unless has_key?(:id)
  self[:id]
end

#initialize_from_array(array) ⇒ Object



190
191
192
193
# File 'lib/map.rb', line 190

def initialize_from_array(array)
  map = self
  Map.each_pair(array){|key, val| map[key] = val}
end

#initialize_from_hash(hash) ⇒ Object



184
185
186
187
188
# File 'lib/map.rb', line 184

def initialize_from_hash(hash)
  map = self
  map.update(hash)
  map.default = hash.default
end

#inspectObject



468
469
470
471
472
# File 'lib/map.rb', line 468

def inspect
  array = []
  each{|key, val| array << (key.inspect + "=>" + val.inspect)}
  string = '{' + array.join(", ") + '}'
end

#invertObject



446
447
448
449
450
451
# File 'lib/map.rb', line 446

def invert
  inverted = klass.allocate
  inverted.default = self.default
  keys.each{|key| inverted[self[key]] = key }
  inverted
end

#key?(key) ⇒ Boolean Also known as: include?, has_key?, member?

Returns:

  • (Boolean)


294
295
296
# File 'lib/map.rb', line 294

def key?(key)
  super(convert_key(key))
end

#keysObject

instance constructor



160
161
162
# File 'lib/map.rb', line 160

def keys
  @keys ||= []
end

#klassObject

support methods



197
198
199
# File 'lib/map.rb', line 197

def klass
  self.class
end

#lastObject



336
337
338
# File 'lib/map.rb', line 336

def last
  [keys.last, self[keys.last]]
end

#map_for(hash) ⇒ Object



206
207
208
# File 'lib/map.rb', line 206

def map_for(hash)
  klass.map_for(hash)
end

#merge(*args) ⇒ Object



307
308
309
# File 'lib/map.rb', line 307

def merge(*args)
  copy.update(*args)
end

#popObject



422
423
424
425
426
427
428
# File 'lib/map.rb', line 422

def pop
  unless empty?
    key = keys.last
    val = delete(key)
    [key, val]
  end
end

#push(*args) ⇒ Object



410
411
412
413
414
415
416
417
418
419
420
# File 'lib/map.rb', line 410

def push(*args)
  Map.each_pair(*args) do |key, val|
    if key?(key)
      delete(key)
    else
      keys.push(key)
    end
    __set__(key, val)
  end
  self
end

#reject(&block) ⇒ Object



453
454
455
# File 'lib/map.rb', line 453

def reject(&block)
  dup.delete_if(&block)
end

#reject!(&block) ⇒ Object



457
458
459
460
# File 'lib/map.rb', line 457

def reject!(&block)
  hash = reject(&block)
  self == hash ? nil : hash
end

#replace(*args) ⇒ Object



383
384
385
386
# File 'lib/map.rb', line 383

def replace(*args)
  clear
  update(*args)
end

#reverse_merge(hash) ⇒ Object



311
312
313
314
315
# File 'lib/map.rb', line 311

def reverse_merge(hash)
  map = copy
  hash.each{|key, val| map[key] = val unless map.key?(key)}
  map
end

#reverse_merge!(hash) ⇒ Object



317
318
319
# File 'lib/map.rb', line 317

def reverse_merge!(hash)
  replace(reverse_merge(hash))
end

#selectObject



462
463
464
465
466
# File 'lib/map.rb', line 462

def select
  array = []
  each{|key, val| array << [key,val] if yield(key, val)}
  array
end

#set(*args) ⇒ Object



603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/map.rb', line 603

def set(*args)
  if args.size == 1 and args.first.is_a?(Hash)
    options = args.shift
  else
    options = {}
    value = args.pop
    keys = args
    options[keys] = value
  end

  options.each do |keys, value|
    keys = Array(keys).flatten

    collection = self
    if keys.size <= 1
      collection[keys.first] = value
      next
    end

    key = nil

    keys.each_cons(2) do |a, b|
      a, b = alphanumeric_key_for(a), alphanumeric_key_for(b)

      case b
        when Numeric
          collection[a] ||= []
          raise(IndexError, "(#{ collection.inspect })[#{ a.inspect }]=#{ value.inspect }") unless collection[a].is_a?(Array)

        when String, Symbol
          collection[a] ||= {}
          raise(IndexError, "(#{ collection.inspect })[#{ a.inspect }]=#{ value.inspect }") unless collection[a].is_a?(Hash)
      end
      collection = collection[a]
      key = b
    end

    collection[key] = value
  end

  return options.values
end

#shiftObject

ordered container specific methods



390
391
392
393
394
395
396
# File 'lib/map.rb', line 390

def shift
  unless empty?
    key = keys.first
    val = delete(key)
    [key, val]
  end
end

#stringify_keysObject



534
# File 'lib/map.rb', line 534

def stringify_keys; dup end

#stringify_keys!Object

oh rails - would that map.rb existed before all this non-sense…



533
# File 'lib/map.rb', line 533

def stringify_keys!; self end

#structObject



41
42
43
# File 'lib/map/struct.rb', line 41

def struct
  @struct ||= Struct.new(map=self)
end

#symbolize_keysObject



536
# File 'lib/map.rb', line 536

def symbolize_keys; dup end

#symbolize_keys!Object



535
# File 'lib/map.rb', line 535

def symbolize_keys!; self end

#to_arrayObject Also known as: to_a



512
513
514
515
516
# File 'lib/map.rb', line 512

def to_array
  array = []
  each{|*pair| array.push(pair)}
  array
end

#to_hashObject



488
489
490
491
492
493
494
495
# File 'lib/map.rb', line 488

def to_hash
  hash = Hash.new(default)
  each do |key, val|
    val = val.to_hash if val.respond_to?(:to_hash)
    hash[key] = val
  end
  hash
end

#to_listObject



519
520
521
522
523
524
525
# File 'lib/map.rb', line 519

def to_list
  list = []
  each_pair do |key, val|
    list[key.to_i] = val if(key.is_a?(Numeric) or key.to_s =~ %r/^\d+$/)
  end
  list
end

#to_optionsObject



538
# File 'lib/map.rb', line 538

def to_options; dup end

#to_options!Object



537
# File 'lib/map.rb', line 537

def to_options!; self end

#to_sObject



527
528
529
# File 'lib/map.rb', line 527

def to_s
  to_array.to_s
end

#to_yaml(*args, &block) ⇒ Object



508
509
510
# File 'lib/map.rb', line 508

def to_yaml(*args, &block)
  as_hash{ super }
end

#unshift(*args) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
# File 'lib/map.rb', line 398

def unshift(*args)
  Map.each_pair(*args) do |key, val|
    if key?(key)
      delete(key)
    else
      keys.unshift(key)
    end
    __set__(key, val)
  end
  self
end

#update(*args) ⇒ Object Also known as: merge!



301
302
303
304
# File 'lib/map.rb', line 301

def update(*args)
  Map.each_pair(*args){|key, val| store(key, val)}
  self
end

#valuesObject Also known as: vals



321
322
323
324
325
# File 'lib/map.rb', line 321

def values
  array = []
  keys.each{|key| array.push(self[key])}
  array
end

#values_at(*keys) ⇒ Object



328
329
330
# File 'lib/map.rb', line 328

def values_at(*keys)
  keys.map{|key| self[key]}
end

#with_indifferent_accessObject



540
# File 'lib/map.rb', line 540

def with_indifferent_access; dup end

#with_indifferent_access!Object



539
# File 'lib/map.rb', line 539

def with_indifferent_access!; self end