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.3.0'
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.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/map.rb', line 144

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



508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/map.rb', line 508

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



616
617
618
619
# File 'lib/map.rb', line 616

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

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



625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/map.rb', line 625

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) ⇒ 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
# File 'lib/map.rb', line 88

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

  return args if size == 0

  if size == 1 and first.respond_to?(:each_pair)
    first.each_pair do |key, val|
      yield(key, val)
    end
    return args
  end

  if size == 1 and first.respond_to?(:each_slice)
    first.each_slice(2) do |key, val|
      yield(key, val)
    end
    return args
  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
      yield(key, val)
    end
  else
    0.step(args.size - 1, 2) do |a|
      key = args[a]
      val = args[a + 1]
      yield(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

.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



642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/map.rb', line 642

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



401
402
403
# File 'lib/map.rb', line 401

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

#==(hash) ⇒ Object

misc



395
396
397
398
399
# File 'lib/map.rb', line 395

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

#=~(hash) ⇒ Object



405
406
407
# File 'lib/map.rb', line 405

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

#[](key) ⇒ Object



249
250
251
# File 'lib/map.rb', line 249

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

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



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

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

#__get__Object



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

alias_method '__get__', '[]'

#__set__Object

writer/reader methods



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

alias_method '__set__', '[]='

#__update__Object



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

alias_method '__update__', 'update'

#alphanumeric_key_for(key) ⇒ Object



621
622
623
# File 'lib/map.rb', line 621

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

#apply(other) ⇒ Object



609
610
611
612
613
614
# File 'lib/map.rb', line 609

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

#as_hashObject



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

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

#classObject



467
468
469
# File 'lib/map.rb', line 467

def class
  @class || super
end

#clearObject



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

def clear
  keys.clear
  super
end

#cloneObject



228
229
230
# File 'lib/map.rb', line 228

def clone
  copy
end

#collection_has_key?(collection, key) ⇒ Boolean

Returns:

  • (Boolean)


556
557
558
559
560
561
562
563
564
# File 'lib/map.rb', line 556

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



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

def conversion_methods
  self.class.conversion_methods
end

#convert(key, val) ⇒ Object



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

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

#convert_key(key) ⇒ Object



187
188
189
# File 'lib/map.rb', line 187

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

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



191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/map.rb', line 191

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

  case value
    when Hash
      klass.coerce(value)
    when Array
      value.map{|v| convert_value(v)}
    else
      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



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

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



232
233
234
# File 'lib/map.rb', line 232

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

#delete(key) ⇒ Object

mutators



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

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

#delete_ifObject



339
340
341
342
343
344
# File 'lib/map.rb', line 339

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



664
665
666
# File 'lib/map.rb', line 664

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

#dupObject



224
225
226
# File 'lib/map.rb', line 224

def dup
  copy
end

#eachObject Also known as: each_pair



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

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

#each_keyObject



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

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

#each_valueObject



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

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

#each_with_indexObject

iterator methods



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

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

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



253
254
255
# File 'lib/map.rb', line 253

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

#firstObject



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

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

#get(*keys) ⇒ Object

support for compound key indexing and depth first iteration



529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/map.rb', line 529

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)


542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/map.rb', line 542

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)


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

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

#initialize_from_array(array) ⇒ Object



170
171
172
173
# File 'lib/map.rb', line 170

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

#initialize_from_hash(hash) ⇒ Object



164
165
166
167
168
# File 'lib/map.rb', line 164

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

#inspectObject



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

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

#invertObject



409
410
411
412
413
414
# File 'lib/map.rb', line 409

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)


257
258
259
# File 'lib/map.rb', line 257

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

#keysObject

instance constructor



140
141
142
# File 'lib/map.rb', line 140

def keys
  @keys ||= []
end

#klassObject

support methods



177
178
179
# File 'lib/map.rb', line 177

def klass
  self.class
end

#lastObject



299
300
301
# File 'lib/map.rb', line 299

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

#map_for(hash) ⇒ Object



181
182
183
184
185
# File 'lib/map.rb', line 181

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

#merge(*args) ⇒ Object



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

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

#popObject



385
386
387
388
389
390
391
# File 'lib/map.rb', line 385

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

#push(*args) ⇒ Object



373
374
375
376
377
378
379
380
381
382
383
# File 'lib/map.rb', line 373

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



416
417
418
# File 'lib/map.rb', line 416

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

#reject!(&block) ⇒ Object



420
421
422
423
# File 'lib/map.rb', line 420

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

#replace(hash) ⇒ Object



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

def replace(hash)
  clear
  update(hash)
end

#reverse_merge(hash) ⇒ Object



274
275
276
277
278
# File 'lib/map.rb', line 274

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

#reverse_merge!(hash) ⇒ Object



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

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

#selectObject



425
426
427
428
429
# File 'lib/map.rb', line 425

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

#set(*args) ⇒ Object



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/map.rb', line 566

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



353
354
355
356
357
358
359
# File 'lib/map.rb', line 353

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

#stringify_keysObject



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

def stringify_keys; dup end

#stringify_keys!Object

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



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

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



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

def symbolize_keys; dup end

#symbolize_keys!Object



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

def symbolize_keys!; self end

#to_arrayObject Also known as: to_a



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

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

#to_hashObject



451
452
453
454
455
456
457
458
# File 'lib/map.rb', line 451

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



482
483
484
485
486
487
488
# File 'lib/map.rb', line 482

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



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

def to_options; dup end

#to_options!Object



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

def to_options!; self end

#to_sObject



490
491
492
# File 'lib/map.rb', line 490

def to_s
  to_array.to_s
end

#to_yaml(*args, &block) ⇒ Object



471
472
473
# File 'lib/map.rb', line 471

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

#unshift(*args) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
# File 'lib/map.rb', line 361

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!



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

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

#valuesObject Also known as: vals



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

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

#values_at(*keys) ⇒ Object



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

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

#with_indifferent_accessObject



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

def with_indifferent_access; dup end

#with_indifferent_access!Object



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

def with_indifferent_access!; self end