jinx 2.1.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.
Files changed (149) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +44 -0
  10. data/Rakefile +41 -0
  11. data/examples/family/README.md +10 -0
  12. data/examples/family/ext/build.xml +35 -0
  13. data/examples/family/ext/src/family/Address.java +68 -0
  14. data/examples/family/ext/src/family/Child.java +24 -0
  15. data/examples/family/ext/src/family/DomainObject.java +26 -0
  16. data/examples/family/ext/src/family/Household.java +36 -0
  17. data/examples/family/ext/src/family/Parent.java +48 -0
  18. data/examples/family/ext/src/family/Person.java +42 -0
  19. data/examples/family/lib/family.rb +15 -0
  20. data/examples/family/lib/family/address.rb +6 -0
  21. data/examples/family/lib/family/domain_object.rb +6 -0
  22. data/examples/family/lib/family/household.rb +6 -0
  23. data/examples/family/lib/family/parent.rb +16 -0
  24. data/examples/family/lib/family/person.rb +6 -0
  25. data/examples/model/README.md +25 -0
  26. data/examples/model/ext/build.xml +35 -0
  27. data/examples/model/ext/src/domain/Child.java +192 -0
  28. data/examples/model/ext/src/domain/Dependent.java +29 -0
  29. data/examples/model/ext/src/domain/DomainObject.java +26 -0
  30. data/examples/model/ext/src/domain/Independent.java +83 -0
  31. data/examples/model/ext/src/domain/Parent.java +129 -0
  32. data/examples/model/ext/src/domain/Person.java +14 -0
  33. data/examples/model/lib/model.rb +13 -0
  34. data/examples/model/lib/model/child.rb +13 -0
  35. data/examples/model/lib/model/domain_object.rb +6 -0
  36. data/examples/model/lib/model/independent.rb +11 -0
  37. data/examples/model/lib/model/parent.rb +17 -0
  38. data/jinx.gemspec +22 -0
  39. data/lib/jinx.rb +3 -0
  40. data/lib/jinx/active_support/README.txt +2 -0
  41. data/lib/jinx/active_support/core_ext/string.rb +7 -0
  42. data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
  43. data/lib/jinx/active_support/inflections.rb +55 -0
  44. data/lib/jinx/active_support/inflector.rb +398 -0
  45. data/lib/jinx/cli/application.rb +36 -0
  46. data/lib/jinx/cli/command.rb +214 -0
  47. data/lib/jinx/helpers/array.rb +108 -0
  48. data/lib/jinx/helpers/boolean.rb +42 -0
  49. data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
  50. data/lib/jinx/helpers/class.rb +149 -0
  51. data/lib/jinx/helpers/collection.rb +33 -0
  52. data/lib/jinx/helpers/collections.rb +11 -0
  53. data/lib/jinx/helpers/collector.rb +20 -0
  54. data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
  55. data/lib/jinx/helpers/enumerable.rb +242 -0
  56. data/lib/jinx/helpers/enumerate.rb +35 -0
  57. data/lib/jinx/helpers/error.rb +15 -0
  58. data/lib/jinx/helpers/file_separator.rb +65 -0
  59. data/lib/jinx/helpers/filter.rb +52 -0
  60. data/lib/jinx/helpers/flattener.rb +38 -0
  61. data/lib/jinx/helpers/hash.rb +12 -0
  62. data/lib/jinx/helpers/hashable.rb +502 -0
  63. data/lib/jinx/helpers/inflector.rb +36 -0
  64. data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
  65. data/lib/jinx/helpers/lazy_hash.rb +44 -0
  66. data/lib/jinx/helpers/log.rb +106 -0
  67. data/lib/jinx/helpers/math.rb +12 -0
  68. data/lib/jinx/helpers/merge.rb +60 -0
  69. data/lib/jinx/helpers/module.rb +18 -0
  70. data/lib/jinx/helpers/multi_enumerator.rb +31 -0
  71. data/lib/jinx/helpers/options.rb +92 -0
  72. data/lib/jinx/helpers/os.rb +19 -0
  73. data/lib/jinx/helpers/partial_order.rb +37 -0
  74. data/lib/jinx/helpers/pretty_print.rb +207 -0
  75. data/lib/jinx/helpers/set.rb +8 -0
  76. data/lib/jinx/helpers/stopwatch.rb +76 -0
  77. data/lib/jinx/helpers/transformer.rb +24 -0
  78. data/lib/jinx/helpers/transitive_closure.rb +55 -0
  79. data/lib/jinx/helpers/uniquifier.rb +50 -0
  80. data/lib/jinx/helpers/validation.rb +33 -0
  81. data/lib/jinx/helpers/visitor.rb +370 -0
  82. data/lib/jinx/import/class_path_modifier.rb +77 -0
  83. data/lib/jinx/import/java.rb +337 -0
  84. data/lib/jinx/importer.rb +240 -0
  85. data/lib/jinx/metadata.rb +155 -0
  86. data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
  87. data/lib/jinx/metadata/dependency.rb +244 -0
  88. data/lib/jinx/metadata/id_alias.rb +23 -0
  89. data/lib/jinx/metadata/introspector.rb +179 -0
  90. data/lib/jinx/metadata/inverse.rb +170 -0
  91. data/lib/jinx/metadata/java_property.rb +169 -0
  92. data/lib/jinx/metadata/propertied.rb +500 -0
  93. data/lib/jinx/metadata/property.rb +401 -0
  94. data/lib/jinx/metadata/property_characteristics.rb +114 -0
  95. data/lib/jinx/resource.rb +862 -0
  96. data/lib/jinx/resource/copy_visitor.rb +36 -0
  97. data/lib/jinx/resource/inversible.rb +90 -0
  98. data/lib/jinx/resource/match_visitor.rb +180 -0
  99. data/lib/jinx/resource/matcher.rb +20 -0
  100. data/lib/jinx/resource/merge_visitor.rb +73 -0
  101. data/lib/jinx/resource/mergeable.rb +185 -0
  102. data/lib/jinx/resource/reference_enumerator.rb +49 -0
  103. data/lib/jinx/resource/reference_path_visitor.rb +38 -0
  104. data/lib/jinx/resource/reference_visitor.rb +55 -0
  105. data/lib/jinx/resource/unique.rb +35 -0
  106. data/lib/jinx/version.rb +3 -0
  107. data/spec/defaults_spec.rb +30 -0
  108. data/spec/definitions/model/alias/child.rb +5 -0
  109. data/spec/definitions/model/base/child.rb +5 -0
  110. data/spec/definitions/model/base/domain_object.rb +5 -0
  111. data/spec/definitions/model/base/independent.rb +5 -0
  112. data/spec/definitions/model/defaults/child.rb +5 -0
  113. data/spec/definitions/model/dependency/child.rb +5 -0
  114. data/spec/definitions/model/dependency/parent.rb +6 -0
  115. data/spec/definitions/model/inverse/child.rb +5 -0
  116. data/spec/definitions/model/inverse/independent.rb +5 -0
  117. data/spec/definitions/model/inverse/parent.rb +5 -0
  118. data/spec/definitions/model/mandatory/child.rb +6 -0
  119. data/spec/dependency_spec.rb +47 -0
  120. data/spec/family_spec.rb +64 -0
  121. data/spec/inverse_spec.rb +53 -0
  122. data/spec/mandatory_spec.rb +43 -0
  123. data/spec/metadata_spec.rb +68 -0
  124. data/spec/resource_spec.rb +30 -0
  125. data/spec/spec_helper.rb +3 -0
  126. data/spec/support/model.rb +19 -0
  127. data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
  128. data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
  129. data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
  130. data/test/fixtures/mixed/ext/build.xml +35 -0
  131. data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
  132. data/test/helper.rb +7 -0
  133. data/test/lib/jinx/command_test.rb +41 -0
  134. data/test/lib/jinx/helpers/boolean_test.rb +27 -0
  135. data/test/lib/jinx/helpers/class_test.rb +60 -0
  136. data/test/lib/jinx/helpers/collections_test.rb +402 -0
  137. data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
  138. data/test/lib/jinx/helpers/inflector_test.rb +11 -0
  139. data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
  140. data/test/lib/jinx/helpers/module_test.rb +24 -0
  141. data/test/lib/jinx/helpers/options_test.rb +66 -0
  142. data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
  143. data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
  144. data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
  145. data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
  146. data/test/lib/jinx/helpers/visitor_test.rb +288 -0
  147. data/test/lib/jinx/import/java_test.rb +78 -0
  148. data/test/lib/jinx/import/mixed_case_test.rb +16 -0
  149. 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