caruby-core 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +51 -0
- data/doc/website/css/site.css +1 -5
- data/doc/website/images/avatar.png +0 -0
- data/doc/website/images/favicon.ico +0 -0
- data/doc/website/images/logo.png +0 -0
- data/doc/website/index.html +82 -0
- data/doc/website/install.html +87 -0
- data/doc/website/quick_start.html +87 -0
- data/doc/website/tissue.html +85 -0
- data/doc/website/uom.html +10 -0
- data/lib/caruby.rb +3 -0
- data/lib/caruby/active_support/README.txt +2 -0
- data/lib/caruby/active_support/core_ext/string.rb +7 -0
- data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/caruby/active_support/inflections.rb +55 -0
- data/lib/caruby/active_support/inflector.rb +398 -0
- data/lib/caruby/cli/application.rb +36 -0
- data/lib/caruby/cli/command.rb +169 -0
- data/lib/caruby/csv/csv_mapper.rb +157 -0
- data/lib/caruby/csv/csvio.rb +185 -0
- data/lib/caruby/database.rb +252 -0
- data/lib/caruby/database/fetched_matcher.rb +66 -0
- data/lib/caruby/database/persistable.rb +432 -0
- data/lib/caruby/database/persistence_service.rb +162 -0
- data/lib/caruby/database/reader.rb +599 -0
- data/lib/caruby/database/saved_merger.rb +131 -0
- data/lib/caruby/database/search_template_builder.rb +59 -0
- data/lib/caruby/database/sql_executor.rb +75 -0
- data/lib/caruby/database/store_template_builder.rb +200 -0
- data/lib/caruby/database/writer.rb +469 -0
- data/lib/caruby/domain/annotatable.rb +25 -0
- data/lib/caruby/domain/annotation.rb +23 -0
- data/lib/caruby/domain/attribute_metadata.rb +447 -0
- data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
- data/lib/caruby/domain/merge.rb +91 -0
- data/lib/caruby/domain/properties.rb +95 -0
- data/lib/caruby/domain/reference_visitor.rb +289 -0
- data/lib/caruby/domain/resource_attributes.rb +528 -0
- data/lib/caruby/domain/resource_dependency.rb +205 -0
- data/lib/caruby/domain/resource_introspection.rb +159 -0
- data/lib/caruby/domain/resource_metadata.rb +117 -0
- data/lib/caruby/domain/resource_module.rb +285 -0
- data/lib/caruby/domain/uniquify.rb +38 -0
- data/lib/caruby/import/annotatable_class.rb +28 -0
- data/lib/caruby/import/annotation_class.rb +27 -0
- data/lib/caruby/import/annotation_module.rb +67 -0
- data/lib/caruby/import/java.rb +338 -0
- data/lib/caruby/migration/migratable.rb +167 -0
- data/lib/caruby/migration/migrator.rb +533 -0
- data/lib/caruby/migration/resource.rb +8 -0
- data/lib/caruby/migration/resource_module.rb +11 -0
- data/lib/caruby/migration/uniquify.rb +20 -0
- data/lib/caruby/resource.rb +969 -0
- data/lib/caruby/util/attribute_path.rb +46 -0
- data/lib/caruby/util/cache.rb +53 -0
- data/lib/caruby/util/class.rb +99 -0
- data/lib/caruby/util/collection.rb +1053 -0
- data/lib/caruby/util/controlled_value.rb +35 -0
- data/lib/caruby/util/coordinate.rb +75 -0
- data/lib/caruby/util/domain_extent.rb +49 -0
- data/lib/caruby/util/file_separator.rb +65 -0
- data/lib/caruby/util/inflector.rb +20 -0
- data/lib/caruby/util/log.rb +95 -0
- data/lib/caruby/util/math.rb +12 -0
- data/lib/caruby/util/merge.rb +59 -0
- data/lib/caruby/util/module.rb +34 -0
- data/lib/caruby/util/options.rb +92 -0
- data/lib/caruby/util/partial_order.rb +36 -0
- data/lib/caruby/util/person.rb +119 -0
- data/lib/caruby/util/pretty_print.rb +184 -0
- data/lib/caruby/util/properties.rb +112 -0
- data/lib/caruby/util/stopwatch.rb +66 -0
- data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
- data/lib/caruby/util/transitive_closure.rb +45 -0
- data/lib/caruby/util/tree.rb +48 -0
- data/lib/caruby/util/trie.rb +37 -0
- data/lib/caruby/util/uniquifier.rb +30 -0
- data/lib/caruby/util/validation.rb +48 -0
- data/lib/caruby/util/version.rb +56 -0
- data/lib/caruby/util/visitor.rb +351 -0
- data/lib/caruby/util/weak_hash.rb +36 -0
- data/lib/caruby/version.rb +3 -0
- 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
|