caruby-core 1.5.5 → 2.1.1

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