caruby-core 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +51 -0
  5. data/doc/website/css/site.css +1 -5
  6. data/doc/website/images/avatar.png +0 -0
  7. data/doc/website/images/favicon.ico +0 -0
  8. data/doc/website/images/logo.png +0 -0
  9. data/doc/website/index.html +82 -0
  10. data/doc/website/install.html +87 -0
  11. data/doc/website/quick_start.html +87 -0
  12. data/doc/website/tissue.html +85 -0
  13. data/doc/website/uom.html +10 -0
  14. data/lib/caruby.rb +3 -0
  15. data/lib/caruby/active_support/README.txt +2 -0
  16. data/lib/caruby/active_support/core_ext/string.rb +7 -0
  17. data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
  18. data/lib/caruby/active_support/inflections.rb +55 -0
  19. data/lib/caruby/active_support/inflector.rb +398 -0
  20. data/lib/caruby/cli/application.rb +36 -0
  21. data/lib/caruby/cli/command.rb +169 -0
  22. data/lib/caruby/csv/csv_mapper.rb +157 -0
  23. data/lib/caruby/csv/csvio.rb +185 -0
  24. data/lib/caruby/database.rb +252 -0
  25. data/lib/caruby/database/fetched_matcher.rb +66 -0
  26. data/lib/caruby/database/persistable.rb +432 -0
  27. data/lib/caruby/database/persistence_service.rb +162 -0
  28. data/lib/caruby/database/reader.rb +599 -0
  29. data/lib/caruby/database/saved_merger.rb +131 -0
  30. data/lib/caruby/database/search_template_builder.rb +59 -0
  31. data/lib/caruby/database/sql_executor.rb +75 -0
  32. data/lib/caruby/database/store_template_builder.rb +200 -0
  33. data/lib/caruby/database/writer.rb +469 -0
  34. data/lib/caruby/domain/annotatable.rb +25 -0
  35. data/lib/caruby/domain/annotation.rb +23 -0
  36. data/lib/caruby/domain/attribute_metadata.rb +447 -0
  37. data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
  38. data/lib/caruby/domain/merge.rb +91 -0
  39. data/lib/caruby/domain/properties.rb +95 -0
  40. data/lib/caruby/domain/reference_visitor.rb +289 -0
  41. data/lib/caruby/domain/resource_attributes.rb +528 -0
  42. data/lib/caruby/domain/resource_dependency.rb +205 -0
  43. data/lib/caruby/domain/resource_introspection.rb +159 -0
  44. data/lib/caruby/domain/resource_metadata.rb +117 -0
  45. data/lib/caruby/domain/resource_module.rb +285 -0
  46. data/lib/caruby/domain/uniquify.rb +38 -0
  47. data/lib/caruby/import/annotatable_class.rb +28 -0
  48. data/lib/caruby/import/annotation_class.rb +27 -0
  49. data/lib/caruby/import/annotation_module.rb +67 -0
  50. data/lib/caruby/import/java.rb +338 -0
  51. data/lib/caruby/migration/migratable.rb +167 -0
  52. data/lib/caruby/migration/migrator.rb +533 -0
  53. data/lib/caruby/migration/resource.rb +8 -0
  54. data/lib/caruby/migration/resource_module.rb +11 -0
  55. data/lib/caruby/migration/uniquify.rb +20 -0
  56. data/lib/caruby/resource.rb +969 -0
  57. data/lib/caruby/util/attribute_path.rb +46 -0
  58. data/lib/caruby/util/cache.rb +53 -0
  59. data/lib/caruby/util/class.rb +99 -0
  60. data/lib/caruby/util/collection.rb +1053 -0
  61. data/lib/caruby/util/controlled_value.rb +35 -0
  62. data/lib/caruby/util/coordinate.rb +75 -0
  63. data/lib/caruby/util/domain_extent.rb +49 -0
  64. data/lib/caruby/util/file_separator.rb +65 -0
  65. data/lib/caruby/util/inflector.rb +20 -0
  66. data/lib/caruby/util/log.rb +95 -0
  67. data/lib/caruby/util/math.rb +12 -0
  68. data/lib/caruby/util/merge.rb +59 -0
  69. data/lib/caruby/util/module.rb +34 -0
  70. data/lib/caruby/util/options.rb +92 -0
  71. data/lib/caruby/util/partial_order.rb +36 -0
  72. data/lib/caruby/util/person.rb +119 -0
  73. data/lib/caruby/util/pretty_print.rb +184 -0
  74. data/lib/caruby/util/properties.rb +112 -0
  75. data/lib/caruby/util/stopwatch.rb +66 -0
  76. data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
  77. data/lib/caruby/util/transitive_closure.rb +45 -0
  78. data/lib/caruby/util/tree.rb +48 -0
  79. data/lib/caruby/util/trie.rb +37 -0
  80. data/lib/caruby/util/uniquifier.rb +30 -0
  81. data/lib/caruby/util/validation.rb +48 -0
  82. data/lib/caruby/util/version.rb +56 -0
  83. data/lib/caruby/util/visitor.rb +351 -0
  84. data/lib/caruby/util/weak_hash.rb +36 -0
  85. data/lib/caruby/version.rb +3 -0
  86. metadata +186 -0
@@ -0,0 +1,46 @@
1
+ require 'caruby/util/validation'
2
+
3
+ # An AttributePath encapsulates an array of attributes that can be evaluated on a source object.
4
+ class AttributePath < Array
5
+ include Validation
6
+
7
+ # Creates an AttributePath from the path Array, String or Symbol. A path string is a period-delimited sequence
8
+ # of attributes, e.g. +person.name+.
9
+ def initialize(path)
10
+ raise ArgumentError.new("Path empty") if path.nil_or_empty?
11
+ # standardize the argument as a symbol array
12
+ attributes = case path
13
+ when Symbol then
14
+ [path]
15
+ when String then
16
+ path.split('.').map { |name| name.to_sym }
17
+ when Array then
18
+ path.map { |name| name.to_sym }
19
+ else
20
+ raise ArgumentError.new("Argument type unsupported - expected Symbol, String or Array; found #{path.class}")
21
+ end
22
+ # make the array
23
+ super(attributes)
24
+ end
25
+
26
+ # Returns the result of evaluating this evaluator's attribute path on the source object.
27
+ # If the evaluation results in a migratable object, then that object is migrated.
28
+ def evaluate(source)
29
+ # call the attribute path as far as possible
30
+ inject(source) do |current, attr|
31
+ return if current.nil?
32
+ evaluate_attribute(attr, current)
33
+ end
34
+ end
35
+
36
+ # Returns the result of evaluating attribute on the source object.
37
+ # If attr is +self+, then the source object is returned.
38
+ def evaluate_attribute(attr, source)
39
+ # call the attribute path as far as possible
40
+ attr == :self ? source : source.send(attr)
41
+ end
42
+
43
+ def to_s
44
+ join('.')
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ require 'caruby/util/collection'
2
+
3
+ module CaRuby
4
+ # Cache for objects held in memory and accessed by key.
5
+ class Cache
6
+
7
+ # The classes which are not cleared when {#clear} is called without the +all+ flag.
8
+ attr_reader :sticky
9
+
10
+ # Returns a new Cache whose value key is determined by calling the given
11
+ # extractor block on the cached value.
12
+ #
13
+ # If the value is not cached and there is a factory Proc, then the result of
14
+ # calling the factory on the missing value is cached with the value key.
15
+ #
16
+ # @param [Proc] optional factory Proc called with a missing value as argument
17
+ # to create a cached object
18
+ def initialize(factory=nil, &extractor)
19
+ @factory = factory
20
+ # Make the class => { key => value } hash.
21
+ # The { key => value } hash takes a value as an argument and converts
22
+ # it to the key by calling the block given to this initializer.
23
+ @hash = LazyHash.new { KeyTransformerHash.new { |value| yield value } }
24
+ @sticky = Set.new
25
+ end
26
+
27
+ # Returns the object cached with the same class and key as the given value.
28
+ # If this Cache has a factory but does not have an entry for value, then the
29
+ # factory is called on the value to create a new entry.
30
+ def [](value)
31
+ chash = @hash[value.class]
32
+ cached = chash[value] if chash
33
+ return cached unless cached.nil? and @factory
34
+ obj = @factory.call(value) || return
35
+ chash[value] = obj
36
+ end
37
+
38
+ # Adds the given value to this cache.
39
+ def add(value)
40
+ @hash[value.class][value] = value
41
+ end
42
+
43
+ # Clears the cache key => object hashes. If all is true, then every class hash
44
+ # is cleared. Otherwise, only the non-sticky classes are cleared.
45
+ def clear(all=false)
46
+ if @sticky.empty? then
47
+ @hash.clear
48
+ else
49
+ @hash.each { |klass, chash| chash.clear unless @sticky.include?(klass) }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,99 @@
1
+ require 'enumerator'
2
+
3
+ class Class
4
+ # Returns an Enumerable on this class and its ancestors.
5
+ def class_hierarchy
6
+ @hierarchy ||= Enumerable::Enumerator.new(self, :each_class_in_hierarchy)
7
+ end
8
+
9
+ # Returns this class's superclass, thereby enabling class ranges, e.g.
10
+ # class A; end
11
+ # class B < A; end
12
+ # (B..Object).to_a #=> [B, A, Object]
13
+ alias :succ :superclass
14
+
15
+ private
16
+
17
+ # Creates an alias for each accessor method of the given attribute.
18
+ #
19
+ # @example
20
+ # class Person
21
+ # attr_reader :social_security_number
22
+ # attr_accessor :postal_code
23
+ # define_attribute_alias(:ssn, :social_security_number)
24
+ # define_attribute_alias(:zip_code, :postal_code)
25
+ # end
26
+ # Person.method_defined?(:ssn) #=> true
27
+ # Person.method_defined?(:ssn=) #=> false
28
+ # Person.method_defined?(:zip_code) #=> true
29
+ # Person.method_defined?(:zip_code=) #=> true
30
+ def define_attribute_alias(aliaz, attribute)
31
+ alias_method(aliaz, attribute) if method_defined?(attribute)
32
+ writer = "#{attribute}=".to_sym
33
+ alias_method("#{aliaz}=".to_sym, writer) if method_defined?(writer)
34
+ end
35
+
36
+ # Creates new accessor methods for each _method_ => _original_ hash entry.
37
+ # The new _method_ offsets the existing Number _original_ attribute value by the given
38
+ # offset (default -1).
39
+ #
40
+ # @example
41
+ # class OneBased
42
+ # attr_accessor :index
43
+ # offset_attr_accessor :zero_based_index => :index
44
+ # end
45
+ #@param [{Symbol => Symbol}] hash the offset => original method hash
46
+ #@param [Integer, nil] offset the offset amount (default is -1)
47
+ def offset_attr_accessor(hash, offset=nil)
48
+ offset ||= -1
49
+ hash.each do |method, original|
50
+ define_method(method) { value = send(original); value + offset if value } if method_defined?(original)
51
+ original_writer = "#{original}=".to_sym
52
+ if method_defined?(original_writer) then
53
+ define_method("#{method}=".to_sym) do |value|
54
+ adjusted = value - offset if value
55
+ send(original_writer, adjusted)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def each_class_in_hierarchy
62
+ current = self
63
+ until current.nil?
64
+ yield current
65
+ current = current.superclass
66
+ end
67
+ end
68
+
69
+ # Redefines method using the given block. The block argument is a new alias for the old method.
70
+ # The block creates a proc which implements the new method body.
71
+ #
72
+ # @example
73
+ # redefine_method(:ssn) { |old_method| lambda { send(old_method).delete('-').to_i } }
74
+ # @return [Symbol] an alias to the old method implementation
75
+ def redefine_method(method)
76
+ # make a new alias id method__base for the existing method.
77
+ # disambiguate with a counter suffix if necessary.
78
+ counter = 2
79
+ # make a valid alias base
80
+ old, eq = /^([^=]*)(=)?$/.match(method.to_s).captures
81
+ old.tr!('|', 'or')
82
+ old.tr!('&', 'and')
83
+ old.tr!('+', 'plus')
84
+ old.tr!('*', 'mult')
85
+ old.tr!('/', 'div')
86
+ old.gsub!(/[^\w]/, 'op')
87
+ base = "redefined__#{old}"
88
+ old_id = "#{base}#{eq}".to_sym
89
+ while method_defined?(old_id)
90
+ base = "#{base}#{counter}"
91
+ old_id = "#{base}#{eq}".to_sym
92
+ counter = counter + 1
93
+ end
94
+ alias_method(old_id, method)
95
+ body = yield old_id
96
+ define_method(method, body)
97
+ old_id
98
+ end
99
+ end
@@ -0,0 +1,1053 @@
1
+ require 'set'
2
+ require 'delegate'
3
+ require 'enumerator'
4
+ require 'generator'
5
+ require 'caruby/util/class'
6
+ require 'caruby/util/validation'
7
+ require 'caruby/util/options'
8
+ require 'caruby/util/pretty_print'
9
+
10
+ class Object
11
+ # Returns whether this object is a collection capable of holding heterogenous items.
12
+ # An Object is a not a collection by default. Subclasses can override this method.
13
+ def collection?
14
+ false
15
+ end
16
+ end
17
+
18
+ module Enumerable
19
+ # Overrides {Object#collection?} to returns +true+, since an Enumerable is capable of
20
+ # holding heterogenous items by default. Subclasses can override this method.
21
+ def collection?
22
+ true
23
+ end
24
+ end
25
+
26
+ class String
27
+ # Overrides {Enumerable#collection?} to returns +false+, since a String is constrained
28
+ # to hold characters.
29
+ def collection?
30
+ false
31
+ end
32
+ end
33
+
34
+ module Enumerable
35
+ # Returns a new Hash generated from this Enumerable and an optional value generator block.
36
+ # This Enumerable contains the Hash keys. If the value generator block is given to this
37
+ # method then the block is called with each enumerated element as an argument to
38
+ # generate the associated hash value. If no block is given, then the values are nil.
39
+ #
40
+ # @example
41
+ # [1, 2, 3].hashify { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
42
+ # [:a].hashify #=> { :a => nil }
43
+ # @return [Hash]
44
+ def hashify
45
+ hash = {}
46
+ each { |item| hash[item] = yield item if block_given? }
47
+ hash
48
+ end
49
+
50
+ # Returns a new Hash generated from this Enumerable and a required value generator block.
51
+ # This Enumerable contains the Hash keys. The block is called with each enumerated
52
+ # element as an argument to generate the associated hash value.
53
+ # Only non-nil, non-empty values are included in the hash.
54
+ #
55
+ # @example
56
+ # [1, 2, 3].to_compact_hash { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
57
+ # [1, 2, 3].to_compact_hash { |n| n.modulo(2) unless item > 2 } #=> {1 => 1, 2 => 0}
58
+ # [1, 2, 3].to_compact_hash { |n| n > 2 } #=> {1 => false, 2 => false, 3 => true}
59
+ # [1, 2, 3].to_compact_hash { |n| Array.new(n - 1, n) } #=> {2 => [2], 3 => [2, 3]}
60
+ # @return [Hash]
61
+ # @raise [ArgumentError] if the generator block is not given
62
+ # @see #hashify
63
+ def to_compact_hash
64
+ raise ArgumentError.new("Compact hash builder is missing the value generator block") unless block_given?
65
+ to_compact_hash_with_index { |item, index| yield item }
66
+ end
67
+
68
+ # Returns a new Hash generated from this Enumerable with a block whose arguments include the enumerated item
69
+ # and its index. nil or empty values are excluded.
70
+ def to_compact_hash_with_index
71
+ hash = {}
72
+ self.each_with_index do |item, index|
73
+ next if item.nil?
74
+ value = yield(item, index)
75
+ next if value.nil_or_empty?
76
+ hash[item] = value
77
+ end
78
+ hash
79
+ end
80
+
81
+ # Returns whether this Enumerable iterates over at least one item.
82
+ #
83
+ # This method is functionally equivalent to +to_a.empty+ but is more concise and efficient.
84
+ def empty?
85
+ not any? { true }
86
+ end
87
+
88
+ # Returns the first enumerated item in this Enumerable, or nil if this Enumerable is empty.
89
+ #
90
+ # This method is functionally equivalent to +to_a.first+ but is more concise and efficient.
91
+ def first
92
+ detect { true }
93
+ end
94
+
95
+ # Returns the count of items enumerated in this Enumerable.
96
+ #
97
+ # This method is functionally equivalent to +to_a.size+ but is more concise and efficient
98
+ # for an Enumerable which does not implement the {#size} method.
99
+ def size
100
+ inject(0) { |size, item| size + 1 }
101
+ end
102
+
103
+ alias :length :size
104
+
105
+ # Prints the content of this Enumerable as a series using {Array#to_series}.
106
+ def to_series(conjunction=nil)
107
+ to_a.to_series
108
+ end
109
+
110
+ # Returns the first non-nil, non-false enumerated value resulting from a call to the block given to this method,
111
+ # or nil if no value detected.
112
+ #
113
+ # @example
114
+ # [1, 2].detect_value { |item| item / 2 if item % 2 == 0 } #=> 1
115
+ # @return [Object] the detected block result
116
+ # @see #detect_with_value
117
+ def detect_value
118
+ each do |*item|
119
+ value = yield(*item)
120
+ return value if value
121
+ end
122
+ nil
123
+ end
124
+
125
+ # Returns the first item and value for which an enumeration on the block given to this method returns
126
+ # a non-nil, non-false value.
127
+ #
128
+ # @example
129
+ # [1, 2].detect_with_value { |item| item / 2 if item % 2 == 0 } #=> [2, 1]
130
+ # @return [(Object, Object)] the detected [item, value] pair
131
+ # @see #detect_value
132
+ def detect_with_value
133
+ value = nil
134
+ match = detect do |*item|
135
+ value = yield(*item)
136
+ end
137
+ [match, value]
138
+ end
139
+
140
+ # Returns a new Enumerable that iterates over the base Enumerable items for which filter evaluates to a non-nil,
141
+ # non-false value, e.g.:
142
+ # [1, 2, 3].filter { |n| n != 2 }.to_a #=> [1, 3]
143
+ #
144
+ # Unlike select, filter reflects changes to the base Enumerable, e.g.:
145
+ # a = [1, 2, 3]
146
+ # filter = a.filter { |n| n != 2 }
147
+ # a << 4
148
+ # filter.to_a #=> [1, 3, 4]
149
+ #
150
+ # In addition, filter has a small, fixed storage requirement, making it preferable to select for large collections.
151
+ # Note, however, that unlike select, filter does not return an Array.
152
+ # The default filter block returns the passed item.
153
+ #
154
+ # @return [Enumerable] the filtered result
155
+ # @example
156
+ # [1, nil, 3].filter.to_a #=> [1, 3]
157
+ def filter(&filter) # :yields: item
158
+ Filter.new(self, &filter)
159
+ end
160
+
161
+ # @return an Enumerable which iterates over the non-nil items in this Enumerable
162
+ def compact
163
+ filter { |item| not item.nil? }
164
+ end
165
+
166
+ # Returns a new Flattener on this Enumerable, e.g.:
167
+ # {:a => {:b => :c}, :d => [:e]}.enum_values.flatten.to_a #=> [:b, :c, :e]
168
+ #
169
+ # @return [Enumerable] the flattened result
170
+ def flatten
171
+ Flattener.new(self).to_a
172
+ end
173
+
174
+ # Returns an Enumerable which iterates over items in this Enumerable and the other Enumerable in sequence, e.g.:
175
+ # [1, 2, 3] + [3, 4] #=> [1, 2, 3, 3, 4]
176
+ #
177
+ # Unlike Array#+, {#union} reflects changes to the underlying enumerators.
178
+ #
179
+ # @example
180
+ # a = [1, 2]
181
+ # b = [4, 5]
182
+ # ab = a.union(b)
183
+ # ab #=> [1, 2, 4, 5]
184
+ # a << 3
185
+ # ab #=> [1, 2, 3, 4, 5]
186
+ # @return [Enumerable] self followed by other
187
+ def union(other)
188
+ MultiEnumerator.new(self, other)
189
+ end
190
+
191
+ alias :+ :union
192
+
193
+ # @return an Enumerable which iterates over items in this Enumerable but not the other Enumerable
194
+ def difference(other)
195
+ filter { |item| not other.include?(item) }
196
+ end
197
+
198
+ alias :- :difference
199
+
200
+ # @return an Enumerable which iterates over items in this Enumerable which are also in the other Enumerable
201
+ def intersect(other)
202
+ filter { |item| other.include?(item) }
203
+ end
204
+
205
+ alias :& :intersect
206
+
207
+ # Returns a new Enumerable that iterates over the base Enumerable applying the transformer block to each item, e.g.:
208
+ # [1, 2, 3].transform { |n| n * 2 }.to_a #=> [2, 4, 6]
209
+ #
210
+ # Unlike #collect, {#wrap} reflects changes to the base Enumerable, e.g.:
211
+ # a = [2, 4, 6]
212
+ ``# transformed = a.wrap { |n| n * 2 }
213
+ # a << 4
214
+ # transformed.to_a #=> [2, 4, 6, 8]
215
+ #
216
+ # In addition, transform has a small, fixed storage requirement, making it preferable to select for large collections.
217
+ # Note, however, that unlike collect, transform does not return an Array.
218
+ def wrap(&transformer) # :yields: item
219
+ Transformer.new(self, &transformer)
220
+ end
221
+
222
+ private
223
+
224
+ class Filter
225
+ include Enumerable
226
+
227
+ def initialize(enum=[], &filter)
228
+ @base = enum
229
+ @filter = filter
230
+ end
231
+
232
+ # Calls block on each item which passes this Filter's filter test.
233
+ def each(&block)
234
+ @base.each { |item| yield(item) if @filter ? @filter.call(item) : item }
235
+ end
236
+
237
+ # Optimized for a Set base.
238
+ def include?(item)
239
+ return false if Set === @base and not @base.include?(item)
240
+ super
241
+ end
242
+
243
+ # Adds value to the base Enumerable, if the base supports it.
244
+ def <<(value)
245
+ @base << value
246
+ end
247
+
248
+ # @return a new Array consisting of this Filter's filtered content merged with the other Enumerable
249
+ def merge(other)
250
+ to_a.merge(other)
251
+ end
252
+
253
+ # Merges the other Enumerable into the base Enumerable, if the base supports it.
254
+ def merge!(other)
255
+ @base.merge!(other)
256
+ end
257
+ end
258
+
259
+ class Transformer
260
+ include Enumerable
261
+
262
+ def initialize(enum=[], &transformer)
263
+ @base = enum
264
+ @xfm = transformer
265
+ end
266
+
267
+ # Sets the base Enumerable on which this Transformer operates and returns this transformer, e.g.:
268
+ # transformer = Transformer.new { |n| n * 2 }
269
+ # transformer.on([1, 2, 3]).to_a #=> [2, 4, 6]
270
+ def on(enum)
271
+ @base = enum
272
+ self
273
+ end
274
+
275
+ # Calls block on each item after this Transformer's transformer block is applied.
276
+ def each
277
+ @base.each { |item| yield(item.nil? ? nil : @xfm.call(item)) }
278
+ end
279
+ end
280
+
281
+ # A MultiEnumerator iterates over several Enumerators in sequence. Unlike Array#+, MultiEnumerator reflects changes to the
282
+ # underlying enumerators, e.g.:
283
+ # a = [1, 2]
284
+ # b = [4, 5]
285
+ # ab = MultiEnumerator.new(a, b)
286
+ # ab.to_a #=> [1, 2, 4, 5]
287
+ # a << 3; b << 6; ab.to_a #=> [1, 2, 3, 4, 5, 6]
288
+ class MultiEnumerator
289
+ include Enumerable
290
+
291
+ # Creates a new MultiEnumerator on the given Enumerator enums.
292
+ def initialize(*enums)
293
+ super()
294
+ @enums = enums
295
+ @enums.compact!
296
+ end
297
+
298
+ # Iterates over each of this MultiEnumerator's Enumerators in sequence.
299
+ def each
300
+ @enums.each { |enum| enum.each { |item| yield item } }
301
+ end
302
+ end
303
+ end
304
+
305
+ # The Collector utility module implements the {on} method to apply a block to
306
+ # a collection transitive closure.
307
+ module Collector
308
+ # Collects the result of applying the given block to the given obj.
309
+ # If obj is a collection, then collects the result of recursively calling this Collector on the enumerated members.
310
+ # If obj is nil, then returns nil.
311
+ # Otherwise, calls block on obj and returns the result.
312
+ #
313
+ # @example
314
+ # Collector.on([1, 2, [3, 4]]) { |n| n * 2 } #=> [2, 4, [6, 8]]]
315
+ # Collector.on(nil) { |n| n * 2 } #=> nil
316
+ # Collector.on(1) { |n| n * 2 } #=> 2
317
+ def self.on(obj, &block)
318
+ obj.collection? ? obj.map { |item| on(item, &block) } : yield(obj) unless obj.nil?
319
+ end
320
+ end
321
+
322
+ class Object
323
+ # Visits this object's enumerable content as follows:
324
+ # * If this object is an Enumerable, then the block given to this method is called on each
325
+ # item in this Enumerable.
326
+ # * Otherwise, if this object is non-nil, then the the block is called on self.
327
+ # * Otherwise, this object is nil and this method is a no-op.
328
+ def enumerate(&block)
329
+ Enumerable === self ? each(&block) : yield(self) unless nil?
330
+ end
331
+
332
+ # Returns an enumerator on this Object. This default implementation returns an Enumerable::Enumerator
333
+ # on enumerate.
334
+ def to_enum
335
+ Enumerable::Enumerator.new(self, :enumerate)
336
+ end
337
+ end
338
+
339
+ module Enumerable
340
+ # @return self
341
+ def to_enum
342
+ self
343
+ end
344
+ end
345
+
346
+ # A Flattener applies a given block to flattened collection content.
347
+ class Flattener
348
+ include Enumerable
349
+
350
+ # Visits the enumerated items in the given object's flattened content.
351
+ # block is called on the base itself if the base is neither nil nor a Enumerable.
352
+ # If the base object is nil or empty, then this method is a no-op and returns nil.
353
+ def self.on(obj, &block)
354
+ obj.collection? ? obj.each { |item| on(item, &block) } : yield(obj) unless obj.nil?
355
+ end
356
+
357
+ # Creates a new Flattener on the given object. obj can be an Enumerable,
358
+ # a single non-collection object or nil.
359
+ def initialize(obj)
360
+ @base = obj
361
+ end
362
+
363
+ # Calls the the given block on this Flattener's flattened content.
364
+ # If the base object is a collection, then the block is called on the flattened content.
365
+ # If the base object is nil, then this method is a no-op.
366
+ # If the base object is neither nil nor a collection, then the block given to this method
367
+ # is called on the base object itself.
368
+ #
369
+ # @example
370
+ # Flattener.new(nil).each { |n| print n } #=>
371
+ # Flattener.new(1).each { |n| print n } #=> 1
372
+ # Flattener.new([1, [2, 3]]).each { |n| print n } #=> 123
373
+ def each(&block)
374
+ Flattener.on(@base, &block)
375
+ end
376
+ end
377
+
378
+ # ConditionalEnumerator applies a filter to another Enumerable, e.g.:
379
+ # ConditionalEnumerator.new([1, 2, 3]) { |i| i < 3 }.to_a #=> [1, 2]
380
+ #
381
+ class ConditionalEnumerator
382
+ include Enumerable
383
+
384
+ # Creates a ConditionalEnumerator which wraps the base Enumerator with a conditional filter.
385
+ def initialize(base, &filter)
386
+ @base = base
387
+ @filter = filter
388
+ end
389
+
390
+ # Applies the iterator block to each of this ConditionalEnumerator's base Enumerable items
391
+ # for which this ConditionalEnumerator's filter returns true.
392
+ def each
393
+ @base.each { |item| (yield item) if @filter.call(item) }
394
+ end
395
+ end
396
+
397
+ # Hashable is a Hash mixin that adds utility methods to a Hash.
398
+ # Hashable can be included by any class or module which implements an _each_ method
399
+ # with arguments _key_ and _value_.
400
+ module Hashable
401
+ include Enumerable
402
+
403
+ # @see Hash#each
404
+ def each_pair(&block)
405
+ each(&block)
406
+ end
407
+
408
+ # @see Hash#[]
409
+ def [](key)
410
+ detect_value { |k, v| v if k == key }
411
+ end
412
+
413
+ # @see Hash#each_key
414
+ def each_key
415
+ each { |key, value| yield key }
416
+ end
417
+
418
+ # @return [Object,nil] the key for which the block given to this method returns a non-nil, non-false value,
419
+ # or nil if none
420
+ def detect_key
421
+ each_key { |key| return key if yield key }
422
+ nil
423
+ end
424
+
425
+ # @see Hash#each_value
426
+ def each_value
427
+ each { |key, value| yield value }
428
+ end
429
+
430
+ # Returns a Hashable which composes each value in this Hashable with the key of
431
+ # the other Hashable, e.g.:
432
+ # x = {:a => :c, :b => :d}
433
+ # y = {:c => 1}
434
+ # z = x.compose(y)
435
+ # z[:a] #=> {:c => 1}
436
+ # z[:b] #=> nil
437
+ #
438
+ # The accessor reflects changes to the underlying hashes, e.g. given the above example:
439
+ # x[:b] = 2
440
+ # z[:b] #=> {:c => 1}
441
+ #
442
+ # Update operations on the result are not supported.
443
+ #
444
+ # @param [Hashable] other the Hashable to compose with this Hashable
445
+ # @return [Hashable] the composed result
446
+ def compose(other)
447
+ transform { |value| {value => other[value]} if other.has_key?(value) }
448
+ end
449
+
450
+ # Returns a Hashable which joins each value in this Hashable with the key of
451
+ # the other Hashable, e.g.:
452
+ # x = {:a => :c, :b => :d}
453
+ # y = {:c => 1}
454
+ # z = x.join(y)
455
+ # z[:a] #=> 1
456
+ # z[:b] #=> nil
457
+ #
458
+ # The accessor reflects changes to the underlying hashes, e.g. given the above example:
459
+ # x[:b] = 2
460
+ # z[:b] #=> 2
461
+ #
462
+ # Update operations on the result are not supported.
463
+ #
464
+ # @param [Hashable] other the Hashable to join with this Hashable
465
+ # @return [Hashable] the joined result
466
+ def join(other)
467
+ transform { |value| other[value] }
468
+ end
469
+
470
+ # Returns a Hashable which associates each key of both this Hashable and the other Hashable
471
+ # with the corresponding value in the first Hashable which has that key, e.g.:
472
+ # x = {:a => 1, :b => 2}
473
+ # y = {:b => 3, :c => 4}
474
+ # z = x + y
475
+ # z[:b] #=> 2
476
+ #
477
+ # The accessor reflects changes to the underlying hashes, e.g. given the above example:
478
+ # x.delete(:b)
479
+ # z[:b] #=> 3
480
+ #
481
+ # Update operations on the result are not supported.
482
+ #
483
+ # @param [Hashable] other the Hashable to form a union with this Hashable
484
+ # @return [Hashable] the union result
485
+ def union(other)
486
+ MultiHash.new(self, other)
487
+ end
488
+
489
+ alias :+ :union
490
+
491
+ # Returns a new Hashable that iterates over the base Hashable <key, value> pairs for which the block
492
+ # given to this method evaluates to a non-nil, non-false value, e.g.:
493
+ # {:a => 1, :b => 2, :c => 3}.filter { |k, v| k != :b }.to_hash #=> {:a => 1, :c => 3}
494
+ #
495
+ # The default filter block tests the value, e.g.:
496
+ # {:a => 1, :b => nil}.filter.to_hash #=> {:a => 1}
497
+ #
498
+ # @yield [key, value] the filter block
499
+ # @return [Hashable] the filtered result
500
+ def filter(&block)
501
+ Filter.new(self, &block)
502
+ end
503
+
504
+ # Optimization of {#filter} for a block that only uses the key.
505
+ #
506
+ # @example
507
+ # {:a => 1, :b => 2, :c => 3}.filter_on_key { |k| k != :b }.to_hash #=> {:a => 1, :c => 3}
508
+ #
509
+ # @yield [key] the filter block
510
+ # @yieldparam key the hash key to filter
511
+ # @return [Hashable] the filtered result
512
+ def filter_on_key(&block)
513
+ KeyFilter.new(self, &block)
514
+ end
515
+
516
+ # @return [Hashable] a {#filter} that only uses the value.
517
+ # @yield [value] the filter block
518
+ # @yieldparam value the hash value to filter
519
+ # @return [Hashable] the filtered result
520
+ def filter_on_value
521
+ filter { |key, value| yield value }
522
+ end
523
+
524
+ # @return [Hash] a {#filter} of this Hashable which excludes the entries with a null value
525
+ def compact
526
+ filter_on_value { |value| not value.nil? }
527
+ end
528
+
529
+ # Partitions this Hashable into two Hashables based on the given filter block
530
+ #
531
+ # @yield [key, value] the partition block
532
+ # @return [(Hashable, Hashable)] the partitioned result
533
+ def hash_partition(&block)
534
+ [filter(&block), filter { |k, v| not yield(k, v) }]
535
+ end
536
+
537
+ # Returns the difference between this Hashable and the other Hashable in a Hash of the form:
538
+ #
539
+ # _key_ => [_mine_, _theirs_]
540
+ #
541
+ # where:
542
+ # * _key_ is the key of association which differs
543
+ # * _mine_ is the value for _key_ in this hash
544
+ # * _theirs_ is the value for _key_ in the other hash
545
+ #
546
+ # @param [Hashable] other the Hashable to subtract
547
+ # @yield [key, v1, v2] the optional block which determines whether values differ (default is equality)
548
+ # @yieldparam key the key for which values are compared
549
+ # @yieldparam v1 the value for key from this Hashable
550
+ # @yieldparam v2 the value for key from the other Hashable
551
+ # @return [{Object => (Object,Object)}] a hash of the differences
552
+ def diff(other)
553
+ (keys.to_set + other.keys).to_compact_hash do |key|
554
+ mine = self[key]
555
+ yours = other[key]
556
+ [mine, yours] unless block_given? ? yield(key, mine, yours) : mine == yours
557
+ end
558
+ end
559
+
560
+ # @yield [key1, key2] the key sort block
561
+ # @return a Hashable whose #each and {#each_pair} enumerations are sorted by key
562
+ def sort(&sorter)
563
+ SortedHash.new(self, &sorter)
564
+ end
565
+
566
+ # Returns a hash which associates each key in this hash with the value mapped by the others.
567
+ #
568
+ # @example
569
+ # {:a => 1, :b => 2}.assoc_values({:a => 3, :c => 4}) #=> {:a => [1, 3], :b => [2, nil], :c => [nil, 4]}
570
+ # {:a => 1, :b => 2}.assoc_values({:a => 3}, {:a => 4, :b => 5}) #=> {:a => [1, 3, 4], :b => [2, nil, 5]}
571
+ #
572
+ # @param [<Hashable>] others the other Hashables to associate with this Hashable
573
+ # @return [Hash] the association hash
574
+ def assoc_values(*others)
575
+ all_keys = keys
576
+ others.each { |hash| all_keys.concat(hash.keys) }
577
+ all_keys.to_compact_hash do |key|
578
+ others.map { |other| other[key] }.unshift(self[key])
579
+ end
580
+ end
581
+
582
+ # Returns an Enumerable whose each block is called on each key which maps to a value which
583
+ # either equals the given target_value or satisfies the filter block.
584
+ #
585
+ # @param target_value the filter value
586
+ # @yield [value] the filter block
587
+ # @return [Enumerable] the filtered keys
588
+ def enum_keys_with_value(target_value=nil, &filter) # :yields: value
589
+ return enum_keys_with_value { |value| value == target_value } if target_value
590
+ filter_on_value(&filter).keys
591
+ end
592
+
593
+ # @return [Enumerable] Enumerable over this Hashable's keys
594
+ def enum_keys
595
+ Enumerable::Enumerator.new(self, :each_key)
596
+ end
597
+
598
+ # @return [Array] this Hashable's keys
599
+ def keys
600
+ enum_keys.to_a
601
+ end
602
+
603
+ # @param key search target
604
+ # @return whether this Hashable has the given key
605
+ def has_key?(key)
606
+ enum_keys.include?(key)
607
+ end
608
+
609
+ alias :include? :has_key?
610
+
611
+ # @return [Enumerable] an Enumerable over this Hashable's values
612
+ def enum_values
613
+ Enumerable::Enumerator.new(self, :each_value)
614
+ end
615
+
616
+ # @yield [key] the key selector
617
+ # @return the keys which satisfy the block given to this method
618
+ def select_keys(&block)
619
+ enum_keys.select(&block)
620
+ end
621
+
622
+ # @yield [key] the key rejector
623
+ # @return the keys which do not satisfy the block given to this method
624
+ def reject_keys(&block)
625
+ enum_keys.reject(&block)
626
+ end
627
+
628
+ # @yield [value] the value selector
629
+ # @return the values which satisfy the block given to this method
630
+ def select_values(&block)
631
+ enum_values.select(&block)
632
+ end
633
+
634
+ # @yield [value] the value rejector
635
+ # @return the values which do not satisfy the block given to this method
636
+ def reject_values(&block)
637
+ enum_values.reject(&block)
638
+ end
639
+
640
+ # @return [Array] this Enumerable's values
641
+ def values
642
+ enum_values.to_a
643
+ end
644
+
645
+ # @param value search target
646
+ # @return whether this Hashable has the given value
647
+ def has_value?(value)
648
+ enum_values.include?(value)
649
+ end
650
+
651
+ # @return [Array] a flattened Array of this Hash
652
+ # @example
653
+ # {:a => {:b => :c}, :d => :e, :f => [:g]} #=> [:a, :b, :c, :d, :e, :f, :g]
654
+ def flatten
655
+ Flattener.new(self).to_a
656
+ end
657
+
658
+ # @yield [key, value] hash splitter
659
+ # @return [Hash] two hashes split by whether calling the block on the
660
+ # entry returns a non-nil, non-false value
661
+ # @example
662
+ # {:a => 1, :b => 2}.partition { |key, value| value < 2 } #=> [{:a => 1}, {:b => 2}]
663
+ def partition(&block)
664
+ super(&block).map { |pairs| pairs.to_assoc_hash }
665
+ end
666
+
667
+ # Returns a new Hash that recursively copies this hash's values. Values of type hash are copied using copy_recursive.
668
+ # Other values are unchanged.
669
+ #
670
+ # This method is useful for preserving and restoring hash associations.
671
+ #
672
+ # @return [Hash] a deep copy of this Hashable
673
+ def copy_recursive
674
+ copy = Hash.new
675
+ keys.each do |key|
676
+ value = self[key]
677
+ copy[key] = Hash === value ? value.copy_recursive : value
678
+ end
679
+ copy
680
+ end
681
+
682
+ # @return [Hash] a new Hash that transforms each value
683
+ # @example
684
+ # {:a => 1, :b => 2}.transform { |n| n * 2 }.values #=> [2, 4]
685
+ def transform(&transformer)
686
+ ValueTransformerHash.new(self, &transformer)
687
+ end
688
+
689
+ def to_hash
690
+ inject({}) { |hash, pair| hash[pair.first] = pair.last; hash }
691
+ end
692
+
693
+ def to_set
694
+ to_a.to_set
695
+ end
696
+
697
+ def to_s
698
+ to_hash.to_s
699
+ end
700
+
701
+ def inspect
702
+ to_hash.inspect
703
+ end
704
+
705
+ def ==(other)
706
+ to_hash == other.to_hash rescue super
707
+ end
708
+
709
+ private
710
+
711
+ # @see #filter
712
+ class Filter
713
+ include Hashable
714
+
715
+ def initialize(base, &filter)
716
+ @base = base
717
+ @filter = filter
718
+ end
719
+
720
+ def each
721
+ @base.each { |k, v| yield(k, v) if @filter ? @filter.call(k, v) : v }
722
+ end
723
+ end
724
+
725
+ # @see #filter_on_key
726
+ class KeyFilter < Filter
727
+ include Hashable
728
+
729
+ def initialize(base)
730
+ super(base) { |k, v| yield(k) }
731
+ end
732
+
733
+ def [](key)
734
+ super if @filter.call(key, nil)
735
+ end
736
+ end
737
+
738
+ # @see #sort
739
+ class SortedHash
740
+ include Hashable
741
+
742
+ def initialize(base, &comparator)
743
+ @base = base
744
+ @comparator = comparator
745
+ end
746
+
747
+ def each
748
+ @base.keys.sort { |k1, k2| @comparator ? @comparator.call(k1, k2) : k1 <=> k2 }.each { |k| yield(k, @base[k]) }
749
+ end
750
+ end
751
+
752
+ # Combines hashes. See Hash#+ for details.
753
+ class MultiHash
754
+ include Hashable
755
+
756
+ def initialize(*hashes)
757
+ @hashes = hashes
758
+ end
759
+
760
+ def [](key)
761
+ @hashes.each { |hash| return hash[key] if hash.has_key?(key) }
762
+ nil
763
+ end
764
+
765
+ def has_key?(key)
766
+ @hashes.any? { |hash| hash.has_key?(key) }
767
+ end
768
+
769
+ def has_value?(value)
770
+ @hashes.any? { |hash| hash.has_value?(value) }
771
+ end
772
+
773
+ def each
774
+ @hashes.each_with_index do |hash, index|
775
+ hash.each do |key, value|
776
+ yield(key, value) unless (0...index).any? { |i| @hashes[i].has_key?(key) }
777
+ end
778
+ end
779
+ self
780
+ end
781
+ end
782
+
783
+ # The ValueTransformerHash class pipes the value from a base Hashable into a transformer block.
784
+ class ValueTransformerHash
785
+ include Hashable
786
+
787
+ # Creates a ValueTransformerHash on the base hash and value transformer block.
788
+ def initialize(base, &transformer) # :yields: value
789
+ @base = base
790
+ @xfm = transformer
791
+ end
792
+
793
+ # Returns the value at key after this ValueTransformerHash's transformer block is applied, or nil
794
+ # if this hash does not contain key.
795
+ def [](key)
796
+ @xfm.call(@base[key]) if @base.has_key?(key)
797
+ end
798
+
799
+ def each
800
+ @base.each { |key, value| yield(key, @xfm.call(value)) }
801
+ end
802
+ end
803
+ end
804
+
805
+ # The KeyTransformerHash class pipes the key access argument into a transformer block before
806
+ # accessing a base Hashable, e.g.:
807
+ # hash = KeyTransformerHash.new { |key| key % 2 }
808
+ # hash[1] = :a
809
+ # hash[3] # => :a
810
+ class KeyTransformerHash
811
+ include Hashable
812
+
813
+ # Creates a KeyTransformerHash on the optional base hash and required key transformer block.
814
+ #
815
+ # Raises ArgumentError if there is no extractor block
816
+ def initialize(base={}, &transformer) # :yields: key
817
+ raise ArgumentError.new("Missing required Accessor block") unless block_given?
818
+ @base = base
819
+ @xfm = transformer
820
+ end
821
+
822
+ # Returns the value at key after this KeyTransformerHash's transformer block is applied to the key,
823
+ # or nil if the base hash does not contain an association for the transforemd key.
824
+ def [](key)
825
+ @base[@xfm.call(key)]
826
+ end
827
+
828
+ # Sets the value at key after this KeyTransformerHash's transformer block is applied, or nil
829
+ # if this hash does not contain an association for the transformed key.
830
+ def []=(key, value)
831
+ @base[@xfm.call(key)] = value
832
+ end
833
+
834
+ # Delegates to the base hash.
835
+ # Note that this breaks the standard Hash contract, since
836
+ # all? { |k, v| self[k] }
837
+ # is not necessarily true because the key is transformed on access.
838
+ # @see Accessor for a KeyTransformerHash variant that restores this contract
839
+ def each(&block)
840
+ @base.each(&block)
841
+ end
842
+ end
843
+
844
+ class Hash
845
+ include Hashable
846
+
847
+ # The EMPTY_HASH constant is an immutable empty hash, used primarily as a default argument.
848
+ class << EMPTY_HASH = Hash.new
849
+ def []=(key, value)
850
+ raise NotImplementedError.new("Modification of the constant empty hash is not supported")
851
+ end
852
+ end
853
+ end
854
+
855
+ # Hashinator creates a Hashable from an Enumerable on [_key_, _value_] pairs.
856
+ # The Hashinator reflects changes to the underlying Enumerable.
857
+ #
858
+ # @example
859
+ # base = [[:a, 1], [:b, 2]]
860
+ # hash = Hashinator.new(base)
861
+ # hash[:a] #=> 1
862
+ # base.first[1] = 3
863
+ # hash[:a] #=> 3
864
+ class Hashinator
865
+ include Hashable
866
+
867
+ def initialize(enum)
868
+ @base = enum
869
+ end
870
+
871
+ def each
872
+ @base.each { |pair| yield(*pair) }
873
+ end
874
+ end
875
+
876
+ #
877
+ # A Hash that creates a new entry on demand.
878
+ #
879
+ class LazyHash < Hash
880
+ #
881
+ # Creates a new Hash with the specified value factory proc.
882
+ # The factory proc has one argument, the key.
883
+ # If access by key fails, then a new association is created
884
+ # from the key to the result of calling the factory proc.
885
+ #
886
+ # Example:
887
+ # hash = LazyHash.new { |key| key.to_s }
888
+ # hash[1] = "1"
889
+ # hash[1] #=> "1"
890
+ # hash[2] #=> "2"
891
+ #
892
+ # If a block is not provided, then the default association value is nil, e.g.:
893
+ # hash = LazyHash.new
894
+ # hash.has_key?(1) #=> false
895
+ # hash[1] #=> nil
896
+ # hash.has_key?(1) #=> true
897
+ #
898
+ # A nil key always returns nil. There is no hash entry for nil, e.g.:
899
+ # hash = LazyHash.new { |key| key }
900
+ # hash[nil] #=> nil
901
+ # hash.has_key?(nil) #=> false
902
+ #
903
+ # If the :compact option is set, then an entry is not created
904
+ # if the value initializer result is nil or empty, e.g.:
905
+ # hash = LazyHash.new { |n| 10.div(n) unless n.zero? }
906
+ # hash[0] #=> nil
907
+ # hash.has_key?(0) #=> false
908
+ def initialize(options=nil)
909
+ reject_flag = Options.get(:compact, options)
910
+ # Make the hash with the factory block
911
+ super() do |hash, key|
912
+ if key then
913
+ value = yield key if block_given?
914
+ hash[key] = value unless reject_flag and value.nil_or_empty?
915
+ end
916
+ end
917
+ end
918
+ end
919
+
920
+ class Array
921
+ # The EMPTY_ARRAY constant is an immutable empty array, used primarily as a default argument.
922
+ class << EMPTY_ARRAY = Array.new
923
+ def <<(value)
924
+ raise NotImplementedError.new("Modification of the constant empty array is not supported")
925
+ end
926
+ end
927
+
928
+ # Relaxes the Ruby Array methods which take an Array argument to allow collection Enumerable arguments.
929
+ [:|, :+, :-, :&].each do |meth|
930
+ redefine_method(meth) do |old_meth|
931
+ lambda { |other| send(old_meth, other.collection? ? other.to_a : other) }
932
+ end
933
+ end
934
+
935
+ redefine_method(:flatten) do |old_meth|
936
+ # if an item is a non-Array collection, then convert it into an array before recursively flattening the list
937
+ lambda { map { |item| item.collection? ? item.to_a : item }.send(old_meth) }
938
+ end
939
+
940
+ # Returns an array containing all but the first item in this Array. This method is syntactic sugar for
941
+ # +self[1..-1]+ or +last(length-1)+
942
+ def rest
943
+ self[1..-1]
944
+ end
945
+
946
+ # Prints the content of this array as a series, e.g.:
947
+ # [1, 2, 3].to_series #=> "1, 2 and 3"
948
+ # [1, 2, 3].to_series('or') #=> "1, 2 or 3"
949
+ #
950
+ # If a block is given to this method, then the block is applied before the series is formed, e.g.:
951
+ # [1, 2, 3].to_series { |n| n + 1 } #=> "2, 3 and 4"
952
+ def to_series(conjunction=nil)
953
+ conjunction ||= 'and'
954
+ return map { |item| yield item }.to_series(conjunction) if block_given?
955
+ padded_conjunction = " #{conjunction} "
956
+ # join all but the last item as a comma-separated list and append the conjunction and last item
957
+ length < 2 ? to_s : self[0...-1].join(', ') + padded_conjunction + last.to_s
958
+ end
959
+
960
+ # Returns a new Hash generated from this array of arrays by associating the first element of each
961
+ # member to the remaining elements. If there are only two elements in the member, then the first
962
+ # element is associated with the second element. If there is less than two elements in the member,
963
+ # the first element is associated with nil. An empty array is ignored.
964
+ #
965
+ # Example:
966
+ # [[:a, 1], [:b, 2, 3], [:c], []].to_assoc_hash #=> { :a => 1, :b => [2,3], :c => nil }
967
+ def to_assoc_hash
968
+ hash = Hash.new
969
+ each do |item|
970
+ raise ArgumentError.new("Array member must be an array: #{item.pp_s(:single_line)}") unless Array === item
971
+ key = item.first
972
+ if item.size < 2 then
973
+ value = nil
974
+ elsif item.size == 2 then
975
+ value = item[1]
976
+ else
977
+ value = item[1..-1]
978
+ end
979
+ hash[key] = value unless key.nil?
980
+ end
981
+ hash
982
+ end
983
+
984
+ alias :base__flatten :flatten
985
+ private :base__flatten
986
+ # Recursively flattens this array, including any collection item that implements the +to_a+ method.
987
+ def flatten
988
+ # if any item is a Set or Java Collection, then convert those into arrays before recursively flattening the list
989
+ if any? { |item| Set === item or Java::JavaUtil::Collection === item } then
990
+ return map { |item| (Set === item or Java::JavaUtil::Collection === item) ? item.to_a : item }.flatten
991
+ end
992
+ base__flatten
993
+ end
994
+
995
+ # Adds the other Enumerable to this array.
996
+ #
997
+ # Raises ArgumentError if other does not respond to the +to_a+ method.
998
+ def add_all(other)
999
+ return concat(other) if Array === other
1000
+ begin
1001
+ return add_all(other.to_a)
1002
+ rescue
1003
+ raise ArgumentError.new("Can't convert #{other.class.name} to array") unless other.respond_to?(:to_a)
1004
+ end
1005
+ end
1006
+
1007
+ alias :merge! :add_all
1008
+ end
1009
+
1010
+ # CaseInsensitiveHash accesses entries in a case-insensitive String comparison. The accessor method
1011
+ # key argument is converted to a String before look-up.
1012
+ #
1013
+ # @example
1014
+ # hash = CaseInsensitiveHash.new
1015
+ # hash[:UP] = "down"
1016
+ # hash['up'] #=> "down"
1017
+ class CaseInsensitiveHash < Hash
1018
+ def initialize
1019
+ super
1020
+ end
1021
+
1022
+ def [](key)
1023
+ # if there is lower-case key association, then convert to lower-case and return.
1024
+ # otherwise, delegate to super with the call argument unchanged. this ensures
1025
+ # that a default block passed to the constructor will be called with the correct
1026
+ # key argument.
1027
+ has_key?(key) ? super(key.to_s.downcase) : super(key)
1028
+ end
1029
+
1030
+ def []=(key, value)
1031
+ super(key.to_s.downcase, value)
1032
+ end
1033
+
1034
+ def has_key?(key)
1035
+ super(key.to_s.downcase)
1036
+ end
1037
+
1038
+ def delete(key)
1039
+ super(key.to_s.downcase)
1040
+ end
1041
+
1042
+ alias :store :[]=
1043
+ alias :include? :has_key?
1044
+ alias :key? :has_key?
1045
+ alias :member? :has_key?
1046
+ end
1047
+
1048
+ class Set
1049
+ # The standard Set {#merge} is an anomaly among Ruby collections, since merge modifies the called Set in-place rather
1050
+ # than return a new Set containing the merged contents. Preserve this unfortunate behavior, but partially address
1051
+ # the anomaly by adding the merge! alias for in-place merge.
1052
+ alias :merge! :merge
1053
+ end