map 6.6.0 → 8.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/map/_lib.rb ADDED
@@ -0,0 +1,85 @@
1
+ class Map
2
+ VERSION = '8.0.0'
3
+
4
+ class << Map
5
+ def version
6
+ VERSION
7
+ end
8
+
9
+ def repo
10
+ 'https://github.com/ahoward/map'
11
+ end
12
+
13
+ def summary
14
+ <<~____
15
+ the perfect ruby data structure
16
+ ____
17
+ end
18
+
19
+ def description
20
+ <<~____
21
+ map.rb is a string/symbol indifferent ordered hash that works in all rubies.
22
+
23
+ out of the over 200 ruby gems i have written, this is the one i use
24
+ every day, in all my projects.
25
+
26
+ some may be accustomed to using ActiveSupport::HashWithIndiffentAccess
27
+ and, although there are some similarities, map.rb is more complete,
28
+ works without requiring a mountain of code, and has been in production
29
+ usage for over 15 years.
30
+
31
+ it has no dependencies, and suports a myriad of other, 'tree-ish'
32
+ operators that will allow you to slice and dice data like a giraffee
33
+ with a giant weed whacker.
34
+ ____
35
+ end
36
+
37
+ def libs
38
+ %w[
39
+ ]
40
+ end
41
+
42
+ def dependencies
43
+ {
44
+ }
45
+ end
46
+
47
+ def libdir(*args, &block)
48
+ @libdir ||= File.dirname(File.expand_path(__FILE__))
49
+ args.empty? ? @libdir : File.join(@libdir, *args)
50
+ ensure
51
+ if block
52
+ begin
53
+ $LOAD_PATH.unshift(@libdir)
54
+ block.call
55
+ ensure
56
+ $LOAD_PATH.shift
57
+ end
58
+ end
59
+ end
60
+
61
+ def load(*libs)
62
+ libs = libs.join(' ').scan(/[^\s+]+/)
63
+ libdir { libs.each { |lib| Kernel.load(lib) } }
64
+ end
65
+
66
+ def load_dependencies!
67
+ libs.each do |lib|
68
+ require lib
69
+ end
70
+
71
+ begin
72
+ require 'rubygems'
73
+ rescue LoadError
74
+ nil
75
+ end
76
+
77
+ has_rubygems = defined?(gem)
78
+
79
+ dependencies.each do |lib, dependency|
80
+ gem(*dependency) if has_rubygems
81
+ require(lib)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,126 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ # Map::Ordering
4
+ #
5
+ # This module contains all ordering-related methods that use the @keys array
6
+ # to maintain insertion order. It is conditionally included in Map only when
7
+ # Ruby's Hash class does not maintain insertion order (Ruby < 1.9), or when
8
+ # explicitly forced via ENV['MAP_FORCE_ORDERING'].
9
+ #
10
+ # On Ruby 1.9+, Hash maintains insertion order natively, so these methods are
11
+ # not needed and Map delegates to Hash's implementation for memory optimization.
12
+
13
+ class Map < Hash
14
+ module Ordering
15
+ # Hook called when module is included into Map class
16
+ # This allows us to inject class methods into Map
17
+ def self.included(base)
18
+ base.class_eval do
19
+ # Override Map.allocate to initialize @keys array
20
+ def self.allocate
21
+ super.instance_eval do
22
+ @keys = []
23
+ self
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Instance methods that track ordering via @keys array
30
+
31
+ def keys
32
+ @keys ||= []
33
+ end
34
+
35
+ def []=(key, val)
36
+ key, val = convert(key, val)
37
+ keys.push(key) unless has_key?(key)
38
+ __set__(key, val)
39
+ end
40
+ alias_method 'store', '[]='
41
+
42
+ def values
43
+ array = []
44
+ keys.each{|key| array.push(self[key])}
45
+ array
46
+ end
47
+ alias_method 'vals', 'values'
48
+
49
+ def first
50
+ [keys.first, self[keys.first]]
51
+ end
52
+
53
+ def last
54
+ [keys.last, self[keys.last]]
55
+ end
56
+
57
+ def each_with_index
58
+ keys.each_with_index{|key, index| yield([key, self[key]], index)}
59
+ self
60
+ end
61
+
62
+ def each_key
63
+ keys.each{|key| yield(key)}
64
+ self
65
+ end
66
+
67
+ def each_value
68
+ keys.each{|key| yield self[key]}
69
+ self
70
+ end
71
+
72
+ def each
73
+ keys.each{|key| yield(key, self[key])}
74
+ self
75
+ end
76
+ alias_method 'each_pair', 'each'
77
+
78
+ def delete(key)
79
+ key = convert_key(key)
80
+ keys.delete(key)
81
+ super(key)
82
+ end
83
+
84
+ def clear
85
+ keys.clear
86
+ super
87
+ end
88
+
89
+ # Array-like ordered operations
90
+ def shift
91
+ unless empty?
92
+ key = keys.first
93
+ val = delete(key)
94
+ [key, val]
95
+ end
96
+ end
97
+
98
+ def unshift(*args)
99
+ Map.each_pair(*args) do |key, val|
100
+ key = convert_key(key)
101
+ delete(key)
102
+ keys.unshift(key)
103
+ __set__(key, val)
104
+ end
105
+ self
106
+ end
107
+
108
+ def push(*args)
109
+ Map.each_pair(*args) do |key, val|
110
+ key = convert_key(key)
111
+ delete(key)
112
+ keys.push(key)
113
+ __set__(key, val)
114
+ end
115
+ self
116
+ end
117
+
118
+ def pop
119
+ unless empty?
120
+ key = keys.last
121
+ val = delete(key)
122
+ [key, val]
123
+ end
124
+ end
125
+ end
126
+ end
data/lib/map.rb CHANGED
@@ -1,41 +1,10 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  class Map < Hash
3
- Version = '6.6.0' unless defined?(Version)
4
- Load = Kernel.method(:load) unless defined?(Load)
3
+ require_relative 'map/_lib.rb'
5
4
 
6
5
  class << Map
7
- def version
8
- Map::Version
9
- end
10
-
11
- def description
12
- "the awesome ruby container you've always wanted: a string/symbol indifferent ordered hash that works in all rubies"
13
- end
14
-
15
- def libdir(*args, &block)
16
- @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
17
- libdir = args.empty? ? @libdir : File.join(@libdir, *args.map{|arg| arg.to_s})
18
- ensure
19
- if block
20
- begin
21
- $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.first==libdir
22
- module_eval(&block)
23
- ensure
24
- $LOAD_PATH.shift() if $LOAD_PATH.first==libdir
25
- end
26
- end
27
- end
28
-
29
- def load(*args, &block)
30
- libdir{ Load.call(*args, &block) }
31
- end
32
-
33
- def allocate
34
- super.instance_eval do
35
- @keys = []
36
- self
37
- end
38
- end
6
+ # allocate method moved to Map::Ordering module (conditionally included)
7
+ # When ordering module is not included (Ruby 1.9+), Hash.allocate is used
39
8
 
40
9
  def new(*args, &block)
41
10
  allocate.instance_eval do
@@ -195,11 +164,10 @@ class Map < Hash
195
164
  end
196
165
  end
197
166
 
198
- # instance constructor
167
+ # instance constructor
199
168
  #
200
- def keys
201
- @keys ||= []
202
- end
169
+ # keys method moved to Map::Ordering module (conditionally included)
170
+ # When ordering module is not included (Ruby 1.9+), Hash#keys is used
203
171
 
204
172
  def initialize(*args, &block)
205
173
  case args.size
@@ -258,6 +226,17 @@ class Map < Hash
258
226
  key.kind_of?(Symbol) ? key.to_s : key
259
227
  end
260
228
 
229
+ def Map.mapify(object)
230
+ case
231
+ when object.is_a?(Array)
232
+ object.each{|it| Map.mapify(it)}
233
+ when object.is_a?(Hash)
234
+ Map.for(object)
235
+ else
236
+ object
237
+ end
238
+ end
239
+
261
240
  def convert_key(key)
262
241
  if klass.respond_to?(:convert_key)
263
242
  klass.convert_key(key)
@@ -332,21 +311,32 @@ class Map < Hash
332
311
  alias_method '__get__', '[]' unless method_defined?('__get__')
333
312
  alias_method '__update__', 'update' unless method_defined?('__update__')
334
313
 
335
- def []=(key, val)
336
- key, val = convert(key, val)
337
- keys.push(key) unless has_key?(key)
338
- __set__(key, val)
314
+ # []= method:
315
+ # - With ordering module (Ruby < 1.9 or forced): tracks keys in @keys array
316
+ # - Without module (Ruby >= 1.9, not forced): just converts and stores
317
+ #
318
+ # Only define for Ruby >= 1.9 without forced ordering (module provides it otherwise)
319
+ unless RUBY_VERSION < '1.9' || ENV['MAP_FORCE_ORDERING']
320
+ def []=(key, val)
321
+ key, val = convert(key, val)
322
+ __set__(key, val)
323
+ end
324
+ alias_method 'store', '[]='
339
325
  end
340
- alias_method 'store', '[]='
341
326
 
342
327
  def [](key)
343
328
  key = convert_key(key)
344
329
  __get__(key)
345
330
  end
346
331
 
347
- def fetch(key, *args, &block)
348
- key = convert_key(key)
349
- super(key, *args, &block)
332
+ def fetch(key, *keys, &block)
333
+ keys.unshift(key)
334
+
335
+ if has?(*keys)
336
+ get(*keys)
337
+ else
338
+ Map.mapify(yield)
339
+ end
350
340
  end
351
341
 
352
342
  def key?(key)
@@ -377,59 +367,67 @@ class Map < Hash
377
367
  replace(reverse_merge(hash))
378
368
  end
379
369
 
380
- def values
381
- array = []
382
- keys.each{|key| array.push(self[key])}
383
- array
384
- end
385
- alias_method 'vals', 'values'
370
+ # Ordering-dependent methods moved to Map::Ordering module (conditionally included):
371
+ # - values, each_with_index, each_key, each_value, each/each_pair (iterate via @keys)
372
+ # - clear (maintain @keys synchronization)
373
+ # When ordering module is not included (Ruby 1.9+), Hash methods are used
386
374
 
387
- def values_at(*keys)
388
- keys.map{|key| self[key]}
389
- end
375
+ # For Ruby >= 1.9 without ordering module, provide optimized implementations
376
+ # These methods are only defined when the ordering module is NOT included
377
+ unless RUBY_VERSION < '1.9' || ENV['MAP_FORCE_ORDERING']
378
+ # delete needs key conversion
379
+ def delete(key)
380
+ key = convert_key(key)
381
+ super(key)
382
+ end
390
383
 
391
- def first
392
- [keys.first, self[keys.first]]
393
- end
384
+ # first and last return [key, value] pairs
385
+ # Use keys array since Hash#first/Hash#last don't exist in all Ruby versions
386
+ def first
387
+ key = keys.first
388
+ [key, self[key]] if key
389
+ end
394
390
 
395
- def last
396
- [keys.last, self[keys.last]]
397
- end
391
+ def last
392
+ key = keys.last
393
+ [key, self[key]] if key
394
+ end
398
395
 
399
- # iterator methods
400
- #
401
- def each_with_index
402
- keys.each_with_index{|key, index| yield([key, self[key]], index)}
403
- self
404
- end
396
+ # values uses Hash#values (ordered in 1.9+)
397
+ def values
398
+ Hash.instance_method(:values).bind(self).call
399
+ end
405
400
 
406
- def each_key
407
- keys.each{|key| yield(key)}
408
- self
409
- end
401
+ # Iterator methods delegate to Hash (ordered in 1.9+)
402
+ def each
403
+ Hash.instance_method(:each).bind(self).call{|k, v| yield(k, v)}
404
+ end
405
+ alias_method 'each_pair', 'each'
410
406
 
411
- def each_value
412
- keys.each{|key| yield self[key]}
413
- self
414
- end
407
+ def each_key
408
+ Hash.instance_method(:each_key).bind(self).call{|k| yield(k)}
409
+ end
415
410
 
416
- def each
417
- keys.each{|key| yield(key, self[key])}
418
- self
419
- end
420
- alias_method 'each_pair', 'each'
411
+ def each_value
412
+ Hash.instance_method(:each_value).bind(self).call{|v| yield(v)}
413
+ end
421
414
 
422
- # mutators
423
- #
424
- def delete(key)
425
- key = convert_key(key)
426
- keys.delete(key)
427
- super(key)
415
+ def each_with_index
416
+ i = 0
417
+ each do |k, v|
418
+ yield([k, v], i)
419
+ i += 1
420
+ end
421
+ self
422
+ end
423
+
424
+ def clear
425
+ Hash.instance_method(:clear).bind(self).call
426
+ end
428
427
  end
429
428
 
430
- def clear
431
- keys.clear
432
- super
429
+ def values_at(*keys)
430
+ keys.map{|key| self[key]}
433
431
  end
434
432
 
435
433
  def delete_if(&block)
@@ -460,39 +458,51 @@ class Map < Hash
460
458
 
461
459
  # ordered container specific methods
462
460
  #
463
- def shift
464
- unless empty?
465
- key = keys.first
466
- val = delete(key)
467
- [key, val]
461
+ # When Map::Ordering module is included (Ruby < 1.9 or forced):
462
+ # These methods use @keys array for efficient order manipulation
463
+ # When module is NOT included (Ruby 1.9+):
464
+ # These methods are defined below and work with Hash's native ordering
465
+ #
466
+ # Only define for Ruby >= 1.9 without forced ordering (module provides them otherwise)
467
+ unless RUBY_VERSION < '1.9' || ENV['MAP_FORCE_ORDERING']
468
+ def shift
469
+ unless empty?
470
+ key = keys.first
471
+ val = delete(key)
472
+ [key, val]
473
+ end
468
474
  end
469
- end
470
475
 
471
- def unshift(*args)
472
- Map.each_pair(*args) do |key, val|
473
- key = convert_key(key)
474
- delete(key)
475
- keys.unshift(key)
476
- __set__(key, val)
476
+ def unshift(*args)
477
+ # For Ruby 1.9+: process each pair in order, unshifting sequentially
478
+ # This matches the @keys array behavior
479
+ Map.each_pair(*args) do |key, val|
480
+ key = convert_key(key)
481
+ val = convert_value(val)
482
+ # Rebuild hash with this key at front
483
+ temp = {key => val}
484
+ each do |k, v|
485
+ temp[k] = v unless k == key
486
+ end
487
+ clear
488
+ temp.each{|k, v| __set__(k, v)}
489
+ end
490
+ self
477
491
  end
478
- self
479
- end
480
492
 
481
- def push(*args)
482
- Map.each_pair(*args) do |key, val|
483
- key = convert_key(key)
484
- delete(key)
485
- keys.push(key)
486
- __set__(key, val)
493
+ def push(*args)
494
+ Map.each_pair(*args) do |key, val|
495
+ self[key] = val # This naturally appends in Ruby 1.9+
496
+ end
497
+ self
487
498
  end
488
- self
489
- end
490
499
 
491
- def pop
492
- unless empty?
493
- key = keys.last
494
- val = delete(key)
495
- [key, val]
500
+ def pop
501
+ unless empty?
502
+ key = keys.last
503
+ val = delete(key)
504
+ [key, val]
505
+ end
496
506
  end
497
507
  end
498
508
 
@@ -636,21 +646,26 @@ class Map < Hash
636
646
  # a sane method missing that only supports writing values or reading
637
647
  # *previously set* values
638
648
  #
639
- def method_missing(*args, &block)
649
+ def method_missing(*args, **kws, &block)
640
650
  method = args.first.to_s
651
+
641
652
  case method
642
653
  when /=$/
643
654
  key = args.shift.to_s.chomp('=')
644
655
  value = args.shift
645
656
  self[key] = value
657
+
646
658
  when /\?$/
647
659
  key = args.shift.to_s.chomp('?')
648
660
  self.has?( key )
661
+
649
662
  else
650
663
  key = method
664
+
651
665
  unless has_key?(key)
652
666
  return(block ? fetch(key, &block) : super(*args))
653
667
  end
668
+
654
669
  self[key]
655
670
  end
656
671
  end
@@ -1172,6 +1187,24 @@ class Map < Hash
1172
1187
  def mongoize
1173
1188
  self
1174
1189
  end
1190
+
1191
+ # Conditionally include ordering module based on Ruby version
1192
+ #
1193
+ # Ruby 1.9+ maintains Hash insertion order natively, so the @keys array
1194
+ # and associated methods are not needed. This provides memory optimization
1195
+ # (25% reduction per Map instance) and delegates to Hash's efficient ordering.
1196
+ #
1197
+ # For Ruby < 1.9, the ordering module provides manual insertion order tracking
1198
+ # via @keys array for backward compatibility.
1199
+ if RUBY_VERSION < '1.9'
1200
+ # Ruby < 1.9: MUST use ordering module (Hash is unordered)
1201
+ require_relative 'map/ordering'
1202
+ include Ordering
1203
+ elsif ENV['MAP_FORCE_ORDERING']
1204
+ # Ruby >= 1.9 ONLY: Optionally force module inclusion for testing legacy code path
1205
+ require_relative 'map/ordering'
1206
+ include Ordering
1207
+ end
1175
1208
  end
1176
1209
 
1177
1210
  module Kernel
@@ -1181,7 +1214,4 @@ private
1181
1214
  end
1182
1215
  end
1183
1216
 
1184
- Map.load('struct.rb')
1185
- Map.load('options.rb')
1186
- Map.load('params.rb')
1187
-
1217
+ require_relative 'map/options.rb'
data/map.gemspec CHANGED
@@ -3,26 +3,38 @@
3
3
 
4
4
  Gem::Specification::new do |spec|
5
5
  spec.name = "map"
6
- spec.version = "6.6.0"
6
+ spec.version = "8.0.0"
7
+ spec.required_ruby_version = '>= 3.0'
7
8
  spec.platform = Gem::Platform::RUBY
8
- spec.summary = "map"
9
- spec.description = "the awesome ruby container you've always wanted: a string/symbol indifferent ordered hash that works in all rubies"
10
- spec.license = "same as ruby's"
9
+ spec.summary = "the perfect ruby data structure"
10
+ spec.description = "map.rb is a string/symbol indifferent ordered hash that works in all rubies.\n\nout of the over 200 ruby gems i have written, this is the one i use\nevery day, in all my projects.\n\nsome may be accustomed to using ActiveSupport::HashWithIndiffentAccess\nand, although there are some similarities, map.rb is more complete,\nworks without requiring a mountain of code, and has been in production\nusage for over 15 years.\n\nit has no dependencies, and suports a myriad of other, 'tree-ish'\noperators that will allow you to slice and dice data like a giraffee\nwith a giant weed whacker."
11
+ spec.license = "Ruby"
11
12
 
12
13
  spec.files =
13
14
  ["LICENSE",
14
15
  "README",
16
+ "README.md",
15
17
  "Rakefile",
16
- "a.rb",
18
+ "images",
19
+ "images/giraffe.jpeg",
20
+ "images/map.png",
17
21
  "lib",
18
22
  "lib/map",
19
23
  "lib/map.rb",
20
- "lib/map/integrations",
21
- "lib/map/integrations/active_record.rb",
24
+ "lib/map/_lib.rb",
22
25
  "lib/map/options.rb",
23
- "lib/map/params.rb",
24
- "lib/map/struct.rb",
26
+ "lib/map/ordering.rb",
25
27
  "map.gemspec",
28
+ "specs",
29
+ "specs/001-ordered-map-module",
30
+ "specs/001-ordered-map-module/checklists",
31
+ "specs/001-ordered-map-module/checklists/requirements.md",
32
+ "specs/001-ordered-map-module/data-model.md",
33
+ "specs/001-ordered-map-module/plan.md",
34
+ "specs/001-ordered-map-module/quickstart.md",
35
+ "specs/001-ordered-map-module/research.md",
36
+ "specs/001-ordered-map-module/spec.md",
37
+ "specs/001-ordered-map-module/tasks.md",
26
38
  "test",
27
39
  "test/leak.rb",
28
40
  "test/lib",
@@ -33,13 +45,10 @@ Gem::Specification::new do |spec|
33
45
 
34
46
  spec.require_path = "lib"
35
47
 
36
- spec.test_files = nil
37
-
38
48
 
39
49
 
40
50
  spec.extensions.push(*[])
41
51
 
42
- spec.rubyforge_project = "codeforpeople"
43
52
  spec.author = "Ara T. Howard"
44
53
  spec.email = "ara.t.howard@gmail.com"
45
54
  spec.homepage = "https://github.com/ahoward/map"