jinx 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +27 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/Rakefile +41 -0
- data/examples/family/README.md +10 -0
- data/examples/family/ext/build.xml +35 -0
- data/examples/family/ext/src/family/Address.java +68 -0
- data/examples/family/ext/src/family/Child.java +24 -0
- data/examples/family/ext/src/family/DomainObject.java +26 -0
- data/examples/family/ext/src/family/Household.java +36 -0
- data/examples/family/ext/src/family/Parent.java +48 -0
- data/examples/family/ext/src/family/Person.java +42 -0
- data/examples/family/lib/family.rb +15 -0
- data/examples/family/lib/family/address.rb +6 -0
- data/examples/family/lib/family/domain_object.rb +6 -0
- data/examples/family/lib/family/household.rb +6 -0
- data/examples/family/lib/family/parent.rb +16 -0
- data/examples/family/lib/family/person.rb +6 -0
- data/examples/model/README.md +25 -0
- data/examples/model/ext/build.xml +35 -0
- data/examples/model/ext/src/domain/Child.java +192 -0
- data/examples/model/ext/src/domain/Dependent.java +29 -0
- data/examples/model/ext/src/domain/DomainObject.java +26 -0
- data/examples/model/ext/src/domain/Independent.java +83 -0
- data/examples/model/ext/src/domain/Parent.java +129 -0
- data/examples/model/ext/src/domain/Person.java +14 -0
- data/examples/model/lib/model.rb +13 -0
- data/examples/model/lib/model/child.rb +13 -0
- data/examples/model/lib/model/domain_object.rb +6 -0
- data/examples/model/lib/model/independent.rb +11 -0
- data/examples/model/lib/model/parent.rb +17 -0
- data/jinx.gemspec +22 -0
- data/lib/jinx.rb +3 -0
- data/lib/jinx/active_support/README.txt +2 -0
- data/lib/jinx/active_support/core_ext/string.rb +7 -0
- data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/jinx/active_support/inflections.rb +55 -0
- data/lib/jinx/active_support/inflector.rb +398 -0
- data/lib/jinx/cli/application.rb +36 -0
- data/lib/jinx/cli/command.rb +214 -0
- data/lib/jinx/helpers/array.rb +108 -0
- data/lib/jinx/helpers/boolean.rb +42 -0
- data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
- data/lib/jinx/helpers/class.rb +149 -0
- data/lib/jinx/helpers/collection.rb +33 -0
- data/lib/jinx/helpers/collections.rb +11 -0
- data/lib/jinx/helpers/collector.rb +20 -0
- data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
- data/lib/jinx/helpers/enumerable.rb +242 -0
- data/lib/jinx/helpers/enumerate.rb +35 -0
- data/lib/jinx/helpers/error.rb +15 -0
- data/lib/jinx/helpers/file_separator.rb +65 -0
- data/lib/jinx/helpers/filter.rb +52 -0
- data/lib/jinx/helpers/flattener.rb +38 -0
- data/lib/jinx/helpers/hash.rb +12 -0
- data/lib/jinx/helpers/hashable.rb +502 -0
- data/lib/jinx/helpers/inflector.rb +36 -0
- data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
- data/lib/jinx/helpers/lazy_hash.rb +44 -0
- data/lib/jinx/helpers/log.rb +106 -0
- data/lib/jinx/helpers/math.rb +12 -0
- data/lib/jinx/helpers/merge.rb +60 -0
- data/lib/jinx/helpers/module.rb +18 -0
- data/lib/jinx/helpers/multi_enumerator.rb +31 -0
- data/lib/jinx/helpers/options.rb +92 -0
- data/lib/jinx/helpers/os.rb +19 -0
- data/lib/jinx/helpers/partial_order.rb +37 -0
- data/lib/jinx/helpers/pretty_print.rb +207 -0
- data/lib/jinx/helpers/set.rb +8 -0
- data/lib/jinx/helpers/stopwatch.rb +76 -0
- data/lib/jinx/helpers/transformer.rb +24 -0
- data/lib/jinx/helpers/transitive_closure.rb +55 -0
- data/lib/jinx/helpers/uniquifier.rb +50 -0
- data/lib/jinx/helpers/validation.rb +33 -0
- data/lib/jinx/helpers/visitor.rb +370 -0
- data/lib/jinx/import/class_path_modifier.rb +77 -0
- data/lib/jinx/import/java.rb +337 -0
- data/lib/jinx/importer.rb +240 -0
- data/lib/jinx/metadata.rb +155 -0
- data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
- data/lib/jinx/metadata/dependency.rb +244 -0
- data/lib/jinx/metadata/id_alias.rb +23 -0
- data/lib/jinx/metadata/introspector.rb +179 -0
- data/lib/jinx/metadata/inverse.rb +170 -0
- data/lib/jinx/metadata/java_property.rb +169 -0
- data/lib/jinx/metadata/propertied.rb +500 -0
- data/lib/jinx/metadata/property.rb +401 -0
- data/lib/jinx/metadata/property_characteristics.rb +114 -0
- data/lib/jinx/resource.rb +862 -0
- data/lib/jinx/resource/copy_visitor.rb +36 -0
- data/lib/jinx/resource/inversible.rb +90 -0
- data/lib/jinx/resource/match_visitor.rb +180 -0
- data/lib/jinx/resource/matcher.rb +20 -0
- data/lib/jinx/resource/merge_visitor.rb +73 -0
- data/lib/jinx/resource/mergeable.rb +185 -0
- data/lib/jinx/resource/reference_enumerator.rb +49 -0
- data/lib/jinx/resource/reference_path_visitor.rb +38 -0
- data/lib/jinx/resource/reference_visitor.rb +55 -0
- data/lib/jinx/resource/unique.rb +35 -0
- data/lib/jinx/version.rb +3 -0
- data/spec/defaults_spec.rb +30 -0
- data/spec/definitions/model/alias/child.rb +5 -0
- data/spec/definitions/model/base/child.rb +5 -0
- data/spec/definitions/model/base/domain_object.rb +5 -0
- data/spec/definitions/model/base/independent.rb +5 -0
- data/spec/definitions/model/defaults/child.rb +5 -0
- data/spec/definitions/model/dependency/child.rb +5 -0
- data/spec/definitions/model/dependency/parent.rb +6 -0
- data/spec/definitions/model/inverse/child.rb +5 -0
- data/spec/definitions/model/inverse/independent.rb +5 -0
- data/spec/definitions/model/inverse/parent.rb +5 -0
- data/spec/definitions/model/mandatory/child.rb +6 -0
- data/spec/dependency_spec.rb +47 -0
- data/spec/family_spec.rb +64 -0
- data/spec/inverse_spec.rb +53 -0
- data/spec/mandatory_spec.rb +43 -0
- data/spec/metadata_spec.rb +68 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model.rb +19 -0
- data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
- data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
- data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
- data/test/fixtures/mixed/ext/build.xml +35 -0
- data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
- data/test/helper.rb +7 -0
- data/test/lib/jinx/command_test.rb +41 -0
- data/test/lib/jinx/helpers/boolean_test.rb +27 -0
- data/test/lib/jinx/helpers/class_test.rb +60 -0
- data/test/lib/jinx/helpers/collections_test.rb +402 -0
- data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
- data/test/lib/jinx/helpers/inflector_test.rb +11 -0
- data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
- data/test/lib/jinx/helpers/module_test.rb +24 -0
- data/test/lib/jinx/helpers/options_test.rb +66 -0
- data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
- data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
- data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
- data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
- data/test/lib/jinx/helpers/visitor_test.rb +288 -0
- data/test/lib/jinx/import/java_test.rb +78 -0
- data/test/lib/jinx/import/mixed_case_test.rb +16 -0
- metadata +272 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'jinx/helpers/class'
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# The EMPTY_ARRAY constant is an immutable empty array, used primarily as a default argument.
|
5
|
+
class << EMPTY_ARRAY ||= Array.new
|
6
|
+
def <<(value)
|
7
|
+
Jinx.fail(NotImplementedError, "Modification of the constant empty array is not supported")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Relaxes the Ruby Array methods which take an Array argument to allow collection Enumerable arguments.
|
12
|
+
[:|, :+, :-, :&].each do |meth|
|
13
|
+
redefine_method(meth) do |old_meth|
|
14
|
+
lambda { |other| send(old_meth, other.collection? ? other.to_a : other) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
redefine_method(:flatten) do |old_meth|
|
19
|
+
# if an item is a non-Array collection, then convert it into an array before recursively flattening the list
|
20
|
+
lambda { map { |item| item.collection? ? item.to_a : item }.send(old_meth) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an array containing all but the first item in this Array. This method is syntactic sugar for
|
24
|
+
# +self[1..-1]+ or +last(length-1)+.
|
25
|
+
#
|
26
|
+
# @return [Array] an array the tail of this array
|
27
|
+
def rest
|
28
|
+
self[1..-1]
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :tail :rest
|
32
|
+
|
33
|
+
# Deletes items from this array which do not satisfy the given block.
|
34
|
+
#
|
35
|
+
# @yield [item] the retention test
|
36
|
+
# @yieldparam item an item in this array
|
37
|
+
# @return [Array] this array
|
38
|
+
def keep_if
|
39
|
+
delete_if { |item| not yield(item) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Prints the content of this array as a series, e.g.:
|
43
|
+
# [1, 2, 3].to_series #=> "1, 2 and 3"
|
44
|
+
# [1, 2, 3].to_series('or') #=> "1, 2 or 3"
|
45
|
+
#
|
46
|
+
# If a block is given to this method, then the block is applied before the series is formed, e.g.:
|
47
|
+
# [1, 2, 3].to_series { |n| n + 1 } #=> "2, 3 and 4"
|
48
|
+
def to_series(conjunction=nil)
|
49
|
+
conjunction ||= 'and'
|
50
|
+
return map { |item| yield item }.to_series(conjunction) if block_given?
|
51
|
+
padded_conjunction = " #{conjunction} "
|
52
|
+
# join all but the last item as a comma-separated list and append the conjunction and last item
|
53
|
+
length < 2 ? to_s : self[0...-1].join(', ') + padded_conjunction + last.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a new Hash generated from this array of arrays by associating the first element of each
|
57
|
+
# member to the remaining elements. If there are only two elements in the member, then the first
|
58
|
+
# element is associated with the second element. If there is less than two elements in the member,
|
59
|
+
# the first element is associated with nil. An empty array is ignored.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# [[:a, 1], [:b, 2, 3], [:c], []].to_assoc_hash #=> { :a => 1, :b => [2,3], :c => nil }
|
63
|
+
# @return [Hash] the first => rest hash
|
64
|
+
def to_assoc_hash
|
65
|
+
hash = {}
|
66
|
+
each do |item|
|
67
|
+
Jinx.fail(ArgumentError, "Array member must be an array: #{item.pp_s(:single_line)}") unless Array === item
|
68
|
+
key = item.first
|
69
|
+
if item.size < 2 then
|
70
|
+
value = nil
|
71
|
+
elsif item.size == 2 then
|
72
|
+
value = item[1]
|
73
|
+
else
|
74
|
+
value = item[1..-1]
|
75
|
+
end
|
76
|
+
hash[key] = value unless key.nil?
|
77
|
+
end
|
78
|
+
hash
|
79
|
+
end
|
80
|
+
|
81
|
+
alias :base__flatten :flatten
|
82
|
+
private :base__flatten
|
83
|
+
# Recursively flattens this array, including any collection item that implements the +to_a+ method.
|
84
|
+
def flatten
|
85
|
+
# if any item is a Set or Java Collection, then convert those into arrays before recursively flattening the list
|
86
|
+
if any? { |item| Set === item or Java::JavaUtil::Collection === item } then
|
87
|
+
return map { |item| (Set === item or Java::JavaUtil::Collection === item) ? item.to_a : item }.flatten
|
88
|
+
end
|
89
|
+
base__flatten
|
90
|
+
end
|
91
|
+
|
92
|
+
# Concatenates the other Enumerable to this array.
|
93
|
+
#
|
94
|
+
# @param [#to_a] other the other Enumerable
|
95
|
+
# @raise [ArgumentError] if other does not respond to the +to_a+ method
|
96
|
+
def add_all(other)
|
97
|
+
return concat(other) if Array === other
|
98
|
+
begin
|
99
|
+
add_all(other.to_a)
|
100
|
+
rescue NoMethodError
|
101
|
+
raise e
|
102
|
+
rescue
|
103
|
+
Jinx.fail(ArgumentError, "Can't convert #{other.class.name} to array")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
alias :merge! :add_all
|
108
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Jinx
|
2
|
+
# Boolean marks the +true+ and +false+ primitive objects.
|
3
|
+
module Boolean
|
4
|
+
# The match for a +true+ String.
|
5
|
+
TRUE_REGEXP = /^(t(rue)?|y(es)?|1)$/i
|
6
|
+
|
7
|
+
# The match for a +false+ String.
|
8
|
+
FALSE_REGEXP = /^(f(alse)?||no?|0)$/i
|
9
|
+
|
10
|
+
# Converts the given object to a Boolean as follows:
|
11
|
+
# * If the object is nil or Boolean, then the unconverted value
|
12
|
+
# * 1 -> true
|
13
|
+
# * 0 -> false
|
14
|
+
# * A {TRUE_REGEXP} match -> true
|
15
|
+
# * A {FALSE_REGEXP} match -> false
|
16
|
+
#
|
17
|
+
# @param for the object to convert
|
18
|
+
# @return [Boolean, nil] the corresponding Boolean
|
19
|
+
# @raise [ArgumentError] if the value cannot be converted
|
20
|
+
def self.for(obj)
|
21
|
+
case obj
|
22
|
+
when nil then nil
|
23
|
+
when true then true
|
24
|
+
when false then false
|
25
|
+
when 1 then true
|
26
|
+
when 0 then false
|
27
|
+
when TRUE_REGEXP then true
|
28
|
+
when FALSE_REGEXP then false
|
29
|
+
else
|
30
|
+
raise ArgumentError.new("Value cannot be converted to boolean: '#{obj}'")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TrueClass
|
37
|
+
include Jinx::Boolean
|
38
|
+
end
|
39
|
+
|
40
|
+
class FalseClass
|
41
|
+
include Jinx::Boolean
|
42
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Jinx
|
2
|
+
# CaseInsensitiveHash accesses entries in a case-insensitive String comparison. The accessor method
|
3
|
+
# key argument is converted to a String before look-up.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# hash = CaseInsensitiveHash.new
|
7
|
+
# hash[:UP] = "down"
|
8
|
+
# hash['up'] #=> "down"
|
9
|
+
class CaseInsensitiveHash < Hash
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](key)
|
15
|
+
# if there is lower-case key association, then convert to lower-case and return.
|
16
|
+
# otherwise, delegate to super with the call argument unchanged. this ensures
|
17
|
+
# that a default block passed to the constructor will be called with the correct
|
18
|
+
# key argument.
|
19
|
+
has_key?(key) ? super(key.to_s.downcase) : super(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, value)
|
23
|
+
super(key.to_s.downcase, value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_key?(key)
|
27
|
+
super(key.to_s.downcase)
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(key)
|
31
|
+
super(key.to_s.downcase)
|
32
|
+
end
|
33
|
+
|
34
|
+
alias :store :[]=
|
35
|
+
alias :include? :has_key?
|
36
|
+
alias :key? :has_key?
|
37
|
+
alias :member? :has_key?
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,149 @@
|
|
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
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Object
|
2
|
+
# Returns whether this object is a collection capable of holding heterogenous items.
|
3
|
+
# An Object is a not a collection by default. Subclasses can override this method.
|
4
|
+
def collection?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Enumerable
|
10
|
+
# Overrides {Object#collection?} to returns +true+, since an Enumerable is capable of
|
11
|
+
# holding heterogenous items by default. Subclasses can override this method.
|
12
|
+
def collection?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class String
|
18
|
+
# Overrides {Enumerable#collection?} to returns +false+, since a String is constrained
|
19
|
+
# to hold characters.
|
20
|
+
def collection?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Jinx
|
26
|
+
module Collection
|
27
|
+
include Enumerable
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Java::JavaUtil::Collection
|
32
|
+
include Jinx::Collection
|
33
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This file loads the definitions of useful collection mix-ins and utility classes.
|
2
|
+
|
3
|
+
require 'jinx/helpers/collection'
|
4
|
+
require 'jinx/helpers/array'
|
5
|
+
require 'jinx/helpers/hashable'
|
6
|
+
require 'jinx/helpers/hash'
|
7
|
+
require 'jinx/helpers/set'
|
8
|
+
require 'jinx/helpers/enumerable'
|
9
|
+
require 'jinx/helpers/enumerate'
|
10
|
+
require 'jinx/helpers/filter'
|
11
|
+
require 'jinx/helpers/flattener'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Jinx
|
2
|
+
# The Collector utility implements the {on} method to apply a block to a collection
|
3
|
+
# transitive closure.
|
4
|
+
module Collector
|
5
|
+
# Collects the result of applying the given block to the given obj.
|
6
|
+
# If obj is a collection, then collects the result of recursively calling this
|
7
|
+
# Collector on the enumerated members.
|
8
|
+
# If obj is nil, then returns nil.
|
9
|
+
# Otherwise, calls block on obj and returns the result.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# Collector.on([1, 2, [3, 4]]) { |n| n * 2 } #=> [2, 4, [6, 8]]]
|
13
|
+
# Collector.on(nil) { |n| n * 2 } #=> nil
|
14
|
+
# Collector.on(1) { |n| n * 2 } #=> 2
|
15
|
+
# @param obj the collection or item to enumerate
|
16
|
+
def self.on(obj, &block)
|
17
|
+
obj.collection? ? obj.map { |item| on(item, &block) } : yield(obj) unless obj.nil?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Jinx
|
2
|
+
# ConditionalEnumerator applies a filter to another Enumerable.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# ConditionalEnumerator.new([1, 2, 3]) { |i| i < 3 }.to_a #=> [1, 2]
|
6
|
+
class ConditionalEnumerator
|
7
|
+
include Collection
|
8
|
+
|
9
|
+
# Creates a ConditionalEnumerator which wraps the base Enumerator with a conditional filter.
|
10
|
+
def initialize(base, &filter)
|
11
|
+
@base = base
|
12
|
+
@filter = filter
|
13
|
+
end
|
14
|
+
|
15
|
+
# Applies the iterator block to each of this ConditionalEnumerator's base Enumerable items
|
16
|
+
# for which this ConditionalEnumerator's filter returns true.
|
17
|
+
def each
|
18
|
+
@base.each { |item| (yield item) if @filter.call(item) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'jinx/helpers/transformer'
|
2
|
+
require 'jinx/helpers/multi_enumerator'
|
3
|
+
|
4
|
+
module Enumerable
|
5
|
+
# Returns a new Hash generated from this Enumerable and an optional value generator block.
|
6
|
+
# This Enumerable contains the Hash keys. If the value generator block is given to this
|
7
|
+
# method then the block is called with each enumerated element as an argument to
|
8
|
+
# generate the associated hash value. If no block is given, then the values are nil.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# [1, 2, 3].hashify { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
|
12
|
+
# [:a].hashify #=> { :a => nil }
|
13
|
+
# @return [Hash]
|
14
|
+
def hashify
|
15
|
+
hash = {}
|
16
|
+
each { |item| hash[item] = yield item if block_given? }
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a new Hash generated from this Enumerable and a required value generator block.
|
21
|
+
# This Enumerable contains the Hash keys. The block is called with each enumerated
|
22
|
+
# element as an argument to generate the associated hash value.
|
23
|
+
# Only non-nil, non-empty values are included in the hash.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# [1, 2, 3].to_compact_hash { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
|
27
|
+
# [1, 2, 3].to_compact_hash { |n| n.modulo(2) unless item > 2 } #=> {1 => 1, 2 => 0}
|
28
|
+
# [1, 2, 3].to_compact_hash { |n| n > 2 } #=> {1 => false, 2 => false, 3 => true}
|
29
|
+
# [1, 2, 3].to_compact_hash { |n| Array.new(n - 1, n) } #=> {2 => [2], 3 => [2, 3]}
|
30
|
+
# @return [Hash]
|
31
|
+
# @raise [ArgumentError] if the generator block is not given
|
32
|
+
# @see #hashify
|
33
|
+
def to_compact_hash
|
34
|
+
Jinx.fail(ArgumentError, "Compact hash builder is missing the value generator block") unless block_given?
|
35
|
+
to_compact_hash_with_index { |item, index| yield item }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a new Hash generated from this Enumerable with a block whose arguments include the enumerated item
|
39
|
+
# and its index. Every value which is nil or empty is excluded.
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# [1, 2, 3].to_compact_hash_with_index { |item, index| item + index } #=> { 1 => 1, 2 => 3, 3 => 5 }
|
43
|
+
# @yield [item, index] the hash value
|
44
|
+
# @yieldparam item the enumerated value
|
45
|
+
# @yieldparam index the enumeration index
|
46
|
+
# @return [Hash] this {Enumerable} converted to a hash by the given block
|
47
|
+
def to_compact_hash_with_index
|
48
|
+
hash = {}
|
49
|
+
self.each_with_index do |item, index|
|
50
|
+
next if item.nil?
|
51
|
+
value = yield(item, index)
|
52
|
+
next if value.nil_or_empty?
|
53
|
+
hash[item] = value
|
54
|
+
end
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
|
58
|
+
# This method is functionally equivalent to +to_a.empty+ but is more concise and efficient.
|
59
|
+
#
|
60
|
+
# @return [Boolean] whether this Enumerable iterates over at least one item
|
61
|
+
def empty?
|
62
|
+
not any? { true }
|
63
|
+
end
|
64
|
+
|
65
|
+
# This method is functionally equivalent to +to_a.first+ but is more concise and efficient.
|
66
|
+
#
|
67
|
+
# @return the first enumerated item in this Enumerable, or nil if this Enumerable is empty
|
68
|
+
def first
|
69
|
+
detect { true }
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method is functionally equivalent to +to_a.last+ but is more concise and efficient.
|
73
|
+
#
|
74
|
+
# @return the last enumerated item in this Enumerable, or nil if this Enumerable is empty
|
75
|
+
def last
|
76
|
+
detect { true }
|
77
|
+
end
|
78
|
+
|
79
|
+
# This method is functionally equivalent to +to_a.size+ but is more concise and efficient
|
80
|
+
# for an Enumerable which does not implement the {#size} method.
|
81
|
+
#
|
82
|
+
# @return [Integer] the count of items enumerated in this Enumerable
|
83
|
+
def size
|
84
|
+
inject(0) { |size, item| size + 1 }
|
85
|
+
end
|
86
|
+
|
87
|
+
alias :length :size
|
88
|
+
|
89
|
+
# @return [String] the content of this Enumerable as a series using {Array#to_series}
|
90
|
+
def to_series(conjunction=nil)
|
91
|
+
to_a.to_series
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the first non-nil, non-false enumerated value resulting from a call to the block given to this method,
|
95
|
+
# or nil if no value detected.
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# [1, 2].detect_value { |item| item / 2 if item % 2 == 0 } #=> 1
|
99
|
+
# @return [Object] the detected block result
|
100
|
+
# @see #detect_with_value
|
101
|
+
def detect_value
|
102
|
+
each do |*item|
|
103
|
+
value = yield(*item)
|
104
|
+
return value if value
|
105
|
+
end
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the first item and value for which an enumeration on the block given to this method returns
|
110
|
+
# a non-nil, non-false value.
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# [1, 2].detect_with_value { |item| item / 2 if item % 2 == 0 } #=> [2, 1]
|
114
|
+
# @return [(Object, Object)] the detected [item, value] pair
|
115
|
+
# @see #detect_value
|
116
|
+
def detect_with_value
|
117
|
+
value = nil
|
118
|
+
match = detect do |*item|
|
119
|
+
value = yield(*item)
|
120
|
+
end
|
121
|
+
[match, value]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns a new Enumerable that iterates over the base Enumerable items for which filter evaluates to a non-nil,
|
125
|
+
# non-false value, e.g.:
|
126
|
+
# [1, 2, 3].filter { |n| n != 2 }.to_a #=> [1, 3]
|
127
|
+
#
|
128
|
+
# Unlike select, filter reflects changes to the base Enumerable, e.g.:
|
129
|
+
# a = [1, 2, 3]
|
130
|
+
# filter = a.filter { |n| n != 2 }
|
131
|
+
# a << 4
|
132
|
+
# filter.to_a #=> [1, 3, 4]
|
133
|
+
#
|
134
|
+
# In addition, filter has a small, fixed storage requirement, making it preferable to select for large collections.
|
135
|
+
# Note, however, that unlike select, filter does not return an Array.
|
136
|
+
# The default filter block returns the passed item.
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# [1, nil, 3].filter.to_a #=> [1, 3]
|
140
|
+
# @yield [item] filter the selection filter
|
141
|
+
# @yieldparam item the collection member to filter
|
142
|
+
# @return [Enumerable] the filtered result
|
143
|
+
def filter(&filter)
|
144
|
+
Jinx::Filter.new(self, &filter)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Enumerable] an iterator over the non-nil items in this Enumerable
|
148
|
+
def compact
|
149
|
+
filter { |item| not item.nil? }
|
150
|
+
end
|
151
|
+
|
152
|
+
# @example
|
153
|
+
# {:a => {:b => :c}, :d => [:e]}.enum_values.flatten.to_a #=> [:b, :c, :e]
|
154
|
+
# @return [Enumerable] the flattened result
|
155
|
+
def flatten
|
156
|
+
Jinx::Flattener.new(self).to_a
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns an Enumerable which iterates over items in this Enumerable and the other Enumerable in sequence.
|
160
|
+
# Unlike the Array plus (+) operator, {#union} reflects changes to the underlying enumerators.
|
161
|
+
#
|
162
|
+
# @example
|
163
|
+
# a = [1, 2]
|
164
|
+
# b = [4, 5]
|
165
|
+
# ab = a.union(b)
|
166
|
+
# ab #=> [1, 2, 4, 5]
|
167
|
+
# a << 3
|
168
|
+
# a + b #=> [1, 2, 4, 5]
|
169
|
+
# ab #=> [1, 2, 3, 4, 5]
|
170
|
+
# @param [Enumerable] other the Enumerable to compose with this Enumerable
|
171
|
+
# @return [Enumerable] an enumerator over self followed by other
|
172
|
+
def union(other)
|
173
|
+
Jinx::MultiEnumerator.new(self, other)
|
174
|
+
end
|
175
|
+
|
176
|
+
alias :+ :union
|
177
|
+
|
178
|
+
# @return an Enumerable which iterates over items in this Enumerable but not the other Enumerable
|
179
|
+
def difference(other)
|
180
|
+
filter { |item| not other.include?(item) }
|
181
|
+
end
|
182
|
+
|
183
|
+
alias :- :difference
|
184
|
+
|
185
|
+
# @return an Enumerable which iterates over items in this Enumerable which are also in the other Enumerable
|
186
|
+
def intersect(other)
|
187
|
+
filter { |item| other.include?(item) }
|
188
|
+
end
|
189
|
+
|
190
|
+
alias :& :intersect
|
191
|
+
|
192
|
+
# Returns a new Enumerable that iterates over the base Enumerable applying the transformer block to each item, e.g.:
|
193
|
+
# [1, 2, 3].transform_value { |n| n * 2 }.to_a #=> [2, 4, 6]
|
194
|
+
#
|
195
|
+
# Unlike Array.map, {#wrap} reflects changes to the base Enumerable, e.g.:
|
196
|
+
# a = [2, 4, 6]
|
197
|
+
``# transformed = a.wrap { |n| n * 2 }
|
198
|
+
# a << 4
|
199
|
+
# transformed.to_a #=> [2, 4, 6, 8]
|
200
|
+
#
|
201
|
+
# In addition, transform has a small, fixed storage requirement, making it preferable to select for large collections.
|
202
|
+
# Note, however, that unlike map, transform does not return an Array.
|
203
|
+
#
|
204
|
+
# @yield [item] the transformer on the enumerated items
|
205
|
+
# @yieldparam item an enumerated item
|
206
|
+
# @return [Enumerable] an enumeration on the transformed values
|
207
|
+
def transform(&mapper)
|
208
|
+
Jinx::Transformer.new(self, &mapper)
|
209
|
+
end
|
210
|
+
|
211
|
+
alias :wrap :transform
|
212
|
+
|
213
|
+
def join(sep = $,)
|
214
|
+
to_a.join(sep)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Sorts this collection's members with a partial sort operator, i.e. the comparison returns -1, 0, 1 or nil.
|
218
|
+
# The resulting sorted order places each non-nil comparable items in the sort order. The order of nil
|
219
|
+
# comparison items is indeterminate.
|
220
|
+
#
|
221
|
+
# @example
|
222
|
+
# [Array, Numeric, Enumerable, Set].partial_sort #=> [Array, Numeric, Set, Enumerable]
|
223
|
+
# @return [Enumerable] the items in this collection in partial sort order
|
224
|
+
def partial_sort
|
225
|
+
unless block_given? then return partial_sort { |item1, item2| item1 <=> item2 } end
|
226
|
+
sort { |item1, item2| yield(item1, item2) or 1 }
|
227
|
+
end
|
228
|
+
|
229
|
+
# Sorts this collection's members with a partial sort operator on the results of applying the block.
|
230
|
+
#
|
231
|
+
# @return [Enumerable] the items in this collection in partial sort order
|
232
|
+
def partial_sort_by
|
233
|
+
partial_sort { |item1, item2| yield(item1) <=> yield(item2) }
|
234
|
+
end
|
235
|
+
|
236
|
+
# @yield [item] the transformer on the enumerated items
|
237
|
+
# @yieldparam item an enumerated item
|
238
|
+
# @return [Enumerable] the mapped values excluding null values
|
239
|
+
def compact_map(&mapper)
|
240
|
+
wrap(&mapper).compact
|
241
|
+
end
|
242
|
+
end
|