jinx 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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