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,19 @@
1
+ require 'rbconfig'
2
+
3
+ module Jinx
4
+ # Operating system methods.
5
+ module OS
6
+ include Config
7
+
8
+ # @return [System] the operating system type +:windows+, +:linux+, +:mac+, +:solaris+, or +:other+
9
+ def self.os_type
10
+ case CONFIG['host_os']
11
+ when /mswin|windows/i then :windows
12
+ when /linux/i then :linux
13
+ when /darwin/i then :mac
14
+ when /sunos|solaris/i then :solaris
15
+ else :other
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ module Jinx
2
+ # A PartialOrder is a Comparable which restricted scope. Classes which include PartialOrder
3
+ # are required to implement the <=> operator with the following semantics:
4
+ # * _a_ <=> _b_ returns -1, 0, or 1 if a and b are comparable, nil otherwise
5
+ # A PartialOrder thus relaxes comparison symmetry, e.g.
6
+ # a < b
7
+ # does not imply
8
+ # b >= a.
9
+ # Example:
10
+ # module Queued
11
+ # attr_reader :queue
12
+ # def <=>(other)
13
+ # queue.index(self) <=> queue.index(other) if queue.equal?(other.queue)
14
+ # end
15
+ # end
16
+ # q1 = [a, b] # a, b are Queued
17
+ # q2 = [c] # c is a Queued
18
+ # a < b #=> true
19
+ # b < c #=> nil
20
+ module PartialOrder
21
+ include Comparable
22
+
23
+ Comparable.instance_methods(false).each do |m|
24
+ define_method(m.to_sym) do |other|
25
+ self <=> other ? super : nil
26
+ end
27
+ end
28
+
29
+ # @return [Boolean] true if other is an instance of this object's class and other == self,
30
+ # false otherwise
31
+ def eql?(other)
32
+ self.class === other and super
33
+ end
34
+
35
+ alias :== :eql?
36
+ end
37
+ end
@@ -0,0 +1,207 @@
1
+ require 'set'
2
+ require 'date'
3
+ require 'pp'
4
+ require 'stringio'
5
+ require 'jinx/helpers/options'
6
+ require 'jinx/helpers/collections'
7
+
8
+ require 'jinx/helpers/inflector'
9
+
10
+ class PrettyPrint
11
+ # The standard +prettyprint+ gem SingleLine is adjusted to add an output accessor and an optional output argument to {#initialize}.
12
+ class SingleLine
13
+ # @return [String] the print target
14
+ attr_reader :output
15
+
16
+ alias :base__initialize :initialize
17
+ private :base__initialize
18
+
19
+ # Overrides the standard SingleLine initializer to supply an output parameter default.
20
+ def initialize(output='', maxwidth=nil, newline=nil)
21
+ base__initialize(output, maxwidth, newline)
22
+ end
23
+ end
24
+ end
25
+
26
+ # A PrintWrapper prints arguments by calling a printer proc.
27
+ class PrintWrapper < Proc
28
+ # Creates a new PrintWrapper on the given arguments.
29
+ def initialize(*args)
30
+ super()
31
+ @args = args
32
+ end
33
+
34
+ # @param args this wrapper's print block parameters
35
+ # @return [PrintWrapper] self
36
+ def wrap(*args)
37
+ @args = args
38
+ self
39
+ end
40
+
41
+ # Calls this PrintWrapper's print procedure on the arguments set in the initializer.
42
+ def to_s
43
+ @args.empty? ? 'nil' : call(*@args)
44
+ end
45
+
46
+ alias :inspect :to_s
47
+ end
48
+
49
+ class Object
50
+ # @return [String] this object's class demodulized name and object id
51
+ def print_class_and_id
52
+ "#{self.class.qp}@#{object_id}"
53
+ end
54
+
55
+ # qp, an abbreviation for quick-print, calls {#print_class_and_id} in this base implementation.
56
+ alias :qp :print_class_and_id
57
+
58
+ # Formats this object with the standard {PrettyPrint}.
59
+ #
60
+ # @param [Hash, Symbol, nil] opts the print options
61
+ # @option opts [Boolean] :single_line print the output on a single line
62
+ # @return [String] the formatted print result
63
+ def pp_s(opts=nil)
64
+ s = StringIO.new
65
+ if Options.get(:single_line, opts) then
66
+ PP.singleline_pp(self, s)
67
+ else
68
+ PP.pp(self, s)
69
+ end
70
+ s.rewind
71
+ s.read.chomp
72
+ end
73
+ end
74
+
75
+ class Numeric
76
+ # Alias #{Object#qp} to {#to_s} in this primitive class.
77
+ alias :qp :to_s
78
+ end
79
+
80
+ class String
81
+ # Alias #{Object#qp} to {#to_s} in this primitive class.
82
+ alias :qp :to_s
83
+ end
84
+
85
+ class TrueClass
86
+ # Alias #{Object#qp} to {#to_s} in this primitive class.
87
+ alias :qp :to_s
88
+ end
89
+
90
+ class FalseClass
91
+ # Alias #{Object#qp} to {#to_s} in this primitive class.
92
+ alias :qp :to_s
93
+ end
94
+
95
+ class NilClass
96
+ # Alias #{Object#qp} to {#to_s} in this primitive class.
97
+ alias :qp :inspect
98
+ end
99
+
100
+ class Symbol
101
+ # Alias #{Object#qp} to {#to_s} in this primitive class.
102
+ alias :qp :inspect
103
+ end
104
+
105
+ class Module
106
+ # @return [String ] the demodulized name
107
+ def qp
108
+ name[/\w+$/]
109
+ end
110
+ end
111
+
112
+ module Enumerable
113
+ # Prints this Enumerable with a filter that calls qp on each item.
114
+ # Non-collection Enumerable classes override this method to delegate to {Object#qp}.
115
+ #
116
+ # Unlike {Object#qp}, this implementation accepts the {Object#pp_s} options.
117
+ # The options are used to format this Enumerable, but are not propagated to the
118
+ # enumerated items.
119
+ #
120
+ # @param (see Object#pp_s)
121
+ # @return [String] the formatted result
122
+ def qp(opts=nil)
123
+ wrap { |item| item.qp }.pp_s(opts)
124
+ end
125
+
126
+ # If a transformer block is given to this method, then the block is applied to each
127
+ # enumerated item before pretty-printing the result.
128
+ #
129
+ # @param (see Object#pp_s)
130
+ # @yield [item] transforms the item to print
131
+ # @yieldparam item the item to print
132
+ # @return (see Oblect#pp_s)
133
+ def pp_s(opts=nil)
134
+ # delegate to Object if no block
135
+ return super unless block_given?
136
+ # make a print wrapper
137
+ wrapper = PrintWrapper.new { |item| yield item }
138
+ # print using the wrapper on each item
139
+ wrap { |item| wrapper.wrap(item) }.pp_s(opts)
140
+ end
141
+
142
+ # Pretty-prints the content within brackets, as is done by the Array pretty printer.
143
+ def pretty_print(q)
144
+ q.group(1, '[', ']') {
145
+ q.seplist(self) { |v|
146
+ q.pp v
147
+ }
148
+ }
149
+ end
150
+
151
+ # Pretty-prints the cycle within brackets, as is done by the Array pretty printer.
152
+ def pretty_print_cycle(q)
153
+ q.text(empty? ? '[]' : '[...]')
154
+ end
155
+ end
156
+
157
+ module Jinx
158
+ module Hashable
159
+ # qp, short for quick-print, prints this Hashable with a filter that calls qp on each key and value.
160
+ #
161
+ # @return [String] the quick-print result
162
+ def qp
163
+ qph = {}
164
+ each { |k, v| qph[k.qp] = v.qp }
165
+ qph.pp_s
166
+ end
167
+
168
+ def pretty_print(q)
169
+ Hash === self ? q.pp_hash(self) : q.pp_hash(to_hash)
170
+ end
171
+
172
+ def pretty_print_cycle(q)
173
+ q.text(empty? ? '{}' : '{...}')
174
+ end
175
+ end
176
+ end
177
+
178
+ class String
179
+ # Pretty-prints this String using the Object pretty_print rather than Enumerable pretty_print.
180
+ def pretty_print(q)
181
+ q.text self
182
+ end
183
+ end
184
+
185
+ class DateTime
186
+ # @return [String] the formatted +strftime+
187
+ def pretty_print(q)
188
+ q.text(strftime)
189
+ end
190
+
191
+ # qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
192
+ alias :qp :to_s
193
+ end
194
+
195
+ class Set
196
+ # Formats this set using {Enumerable#pretty_print}.
197
+ def pretty_print(q)
198
+ # mark this object as visited; this fragment is inferred from pp.rb and is necessary to detect a cycle
199
+ Thread.current[:__inspect_key__] << __id__
200
+ to_a.pretty_print(q)
201
+ end
202
+
203
+ # The pp.rb default pretty printing method for general objects that are detected as part of a cycle.
204
+ def pretty_print_cycle(q)
205
+ to_a.pretty_print_cycle(q)
206
+ end
207
+ end
@@ -0,0 +1,8 @@
1
+ require 'set'
2
+
3
+ class Set
4
+ # The standard Set {#merge} is an anomaly among Ruby collections, since merge modifies the called Set in-place rather
5
+ # than return a new Set containing the merged contents. Preserve this unfortunate behavior, but partially address
6
+ # the anomaly by adding the merge! alias to make it clear that this is an in-place merge.
7
+ alias :merge! :merge
8
+ end
@@ -0,0 +1,76 @@
1
+ require 'benchmark'
2
+
3
+ module Jinx
4
+ # Stopwatch is a simple execution time accumulator.
5
+ class Stopwatch
6
+ # Time accumulates elapsed real time and total CPU time.
7
+ class Time
8
+ # @return [Benchmark::Tms] the Tms wrapped by this Time
9
+ attr_reader :tms
10
+
11
+ # @param [Benchmark::Tms, nil] the starting time (default is now)
12
+ def initialize(tms=nil)
13
+ @tms = tms || Benchmark::Tms.new
14
+ end
15
+
16
+ # @return [Numeric] the cumulative elapsed real clock time
17
+ def elapsed
18
+ @tms.real
19
+ end
20
+
21
+ # @return [Numeric] the cumulative CPU total time
22
+ def cpu
23
+ @tms.total
24
+ end
25
+
26
+ # Adds the time to execute the given block to this time.
27
+ #
28
+ # @return [Numeric] the split execution Time
29
+ def split(&block)
30
+ stms = Benchmark.measure(&block)
31
+ @tms += stms
32
+ Time.new(stms)
33
+ end
34
+
35
+ # Sets this benchmark timer to zero.
36
+ def reset
37
+ @tms = Benchmark::Tms.new
38
+ end
39
+ end
40
+
41
+ # Executes the given block
42
+ #
43
+ # @return [Numeric] the execution Time
44
+ def self.measure(&block)
45
+ new.run(&block)
46
+ end
47
+
48
+ # Creates a new idle Stopwatch.
49
+ def initialize
50
+ @time = Time.new
51
+ end
52
+
53
+ # Executes the given block. Accumulates the execution time in this Stopwatch.
54
+ #
55
+ # @return [Numeric] the execution run Time
56
+ def run(&block)
57
+ @time.split(&block)
58
+ end
59
+
60
+ # @return [Numeric] the cumulative elapsed real clock time spent in {#run} executions
61
+ def elapsed
62
+ @time.elapsed
63
+ end
64
+
65
+ # @return [Numeric] the cumulative CPU total time spent in {#run} executions for the
66
+ # current process and its children
67
+ def cpu
68
+ @time.cpu
69
+ end
70
+
71
+ # Resets this Stopwatch's cumulative time to zero.
72
+ def reset
73
+ @time.reset
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ module Jinx
2
+ # This Transformer helper class applies a transformer block to a base enumeration.
3
+ class Transformer
4
+ include Collection
5
+
6
+ def initialize(enum=[], &transformer)
7
+ @base = enum
8
+ @xfm = transformer
9
+ end
10
+
11
+ # Sets the base Enumerable on which this Transformer operates and returns this transformer, e.g.:
12
+ # transformer = Transformer.new { |n| n * 2 }
13
+ # transformer.on([1, 2, 3]).to_a #=> [2, 4, 6]
14
+ def on(enum)
15
+ @base = enum
16
+ self
17
+ end
18
+
19
+ # Calls the block on each item after this Transformer's transformer block is applied.
20
+ def each
21
+ @base.each { |item| yield(item.nil? ? nil : @xfm.call(item)) }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ require 'jinx/helpers/visitor'
2
+
3
+ class Object
4
+ # Returns the transitive closure over a method or block. This method returns an array partially ordered
5
+ # by the children method or block, i.e. each node occurs before all other nodes referenced directly or
6
+ # indirectly by the children.
7
+ #
8
+ # If a method symbol or name is provided, then that method is called. Otherwise, the block is called.
9
+ # In either case, the call is expected to return an object or Enumerable of objects which also respond
10
+ # to the method or block.
11
+ #
12
+ # @param [Symbol, nil] method the child reference, or nil if a block is given
13
+ # @yield [node] the parent node's children
14
+ # @yieldparam node the parent node
15
+ # @example
16
+ # class Node
17
+ # attr_reader :parent, :children
18
+ # def initialize(name, parent=nil)
19
+ # super()
20
+ # @name = name
21
+ # @parent = parent
22
+ # @children = []
23
+ # parent.children << self if parent
24
+ # end
25
+ #
26
+ # def to_s;
27
+ # end
28
+ # a = Node.new('a'); b = Node.new('b', a), c = Node.new('c', a); d = Node.new('d', c)
29
+ # a.transitive_closure { |node| node.children }.to_a.join(", ") #=> a, b, c, d
30
+ # a.transitive_closure(:children).to_a.join(", ") #=> a, b, c, d
31
+ def transitive_closure(method=nil)
32
+ Jinx.fail(ArgumentError, "Missing both a method argument and a block") if method.nil? and not block_given?
33
+ # If there is a method argument, then the transitive closure is based on that method.
34
+ # Otherwise, visit the closure in reverse depth-first order.
35
+ if method then
36
+ transitive_closure() { |node| node.send(method) }
37
+ else
38
+ Jinx::Visitor.new(:depth_first) { |node| yield node }.to_enum(self).to_a.reverse
39
+ end
40
+ end
41
+ end
42
+
43
+ module Enumerable
44
+ # Returns the transitive closure over all items in this Enumerable.
45
+ #
46
+ # @see Object#transitive_closure
47
+ def transitive_closure(method=nil)
48
+ # delegate to Object if there is a method argument
49
+ return super(method) if method
50
+ # this Enumerable's children are this Enumerable's contents
51
+ closure = super() { |node| node.equal?(self) ? self : yield(node) }
52
+ # remove this collection from the closure
53
+ closure[1..-1]
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ require 'singleton'
2
+ require 'jinx/helpers/lazy_hash'
3
+
4
+ module Jinx
5
+ # A utility class to generate value qualifiers.
6
+ class Uniquifier
7
+ include Singleton
8
+
9
+ # Returns a relatively unique integral qualifier. Successive calls to this method
10
+ # within the same time zone spaced more than a millisecond apart return different
11
+ # integers. Each generated qualifier is greater than the previous by an unspecified
12
+ # amount.
13
+ def self.qualifier
14
+ # the first date that this method could be called
15
+ @first ||= Date.new(2011, 12, 01)
16
+ # days as integer + milliseconds as fraction since the first date
17
+ diff = DateTime.now - @first
18
+ # shift a tenth of a milli up into the integer portion
19
+ decimillis = diff * 24 * 60 * 60 * 10000
20
+ # truncate the fraction
21
+ decimillis.truncate
22
+ end
23
+
24
+ def initialize
25
+ @cache = Jinx::LazyHash.new { Hash.new }
26
+ end
27
+
28
+ # @param obj the object containing the value
29
+ # @param value the value to make unique
30
+ # @return the new unique value, or nil if the given value is nil
31
+ def uniquify(obj, value)
32
+ @cache[obj.class][value] ||= value.uniquify if value
33
+ end
34
+
35
+ def clear
36
+ @cache.clear
37
+ end
38
+ end
39
+ end
40
+
41
+ class String
42
+ # Returns a relatively unique value obtained from the specified base value.
43
+ # The suffix is generated by {Jinx::Uniquifier.qualifier}. Spaces are removed.
44
+ #
45
+ # @example
46
+ # 'Test Name'.uniquify #=> Test_Name_330938800614
47
+ def uniquify
48
+ gsub(' ', '_') + "_#{Jinx::Uniquifier.qualifier}"
49
+ end
50
+ end